Step1: Open this .RMD file in R Studio and click the “Preview” button above. A formatted version should pop up in a browser window. IF not, troubleshoot that!

manual outline

  1. Housekeeping
    1. SET WORKING DIRECTORY
    2. this R Markdown (.RMD) document was created using R Studio
      1. plain text
      2. “Reproducible Research” Movement
    3. R from https://www.r-project.org/
    4. R Studio from https://www.rstudio.com/products/rstudio/download/
    5. Environment tab (Rstudio)
    6. Files/Plots/Packages/Help/Viewer tab (Rstudio)
    7. Preview/view as HTML (R Markdown in Rstudio)
    8. Outline tab (R Markdown in Rstudio)

intro

ascii font/art from http://www.network-science.de/ascii/

#                       __  .__    .___      
#                     _/  |_|__| __| _/__.__.
#                     \   __\  |/ __ <   |  |
#                      |  | |  / /_/ |\___  |
#                      |__| |__\____ |/ ____|
#                                   \/\/     
#                                    \/
#                                    ||
#                                    ||
#                                    ||
#              _                     /\               
#             | |_ _ _ __ _ _ _  ___/ _|___ _ _ _ __  
#             |  _| '_/ _` | ' \(_-<  _/ _ \ '_| '  \ 
#              \__|_| \__,_|_||_/__/_| \___/_| |_|_|_|
#                        //                      \\
#                       //                        \\
#                      //                          \\
#                     //                            \\
#          _         //   _ _                        \\   _     _ 
#     __ _(_)____  _ __ _| (_)______        _ __  ___  __| |___| |
#     \ V / (_-< || / _` | | |_ / -_)======| '  \/ _ \/ _` / -_) |
#      \_/|_/__/\_,_\__,_|_|_/__\___|      |_|_|_\___/\__,_\___|_|

This is primarily an excercise in data wrangling (tidy+transform) and markdown formatting using R Studio. In that context, this analysis is a preliminary exploration of potential metrics for Hop Freshening Power (synonyms: ‘dryhop creep’, ‘ABV creep’, ‘dry-hop creep’) and to model this aspect of the warm-dryhopping process.

Statement of Problem

The warm-dryhopping process can profoundly impact the body, alcohol and vicinal diketone content of the beer. A common metric for this phenomenon has not been established.

Overview of Experimental Data

Through experience in brewing we know that endpoints of dryhopping include:

  • flavor impact (always)
  • impact on visual/presentation (sometimes)
  • ethanol increase accompanied by decrease in specific gravity (sometimes)
  • CO2 increase (sometimes)
  • diacetyl/VDK increase (sometimes)
  • and so on…

Experiments were carried out where

  1. multiple replicate 250-mL samples of [nearly] end-fermented beer were collected from fermenters into 12oz amber bottles
  2. the samples were randomized into groups of three, then each group was either
    • dry-hopped at ~1.0 pounds/bbl (treatment), or
    • not dry-hopped (control)
  3. all were hand-crimped with foil, stored for various times (mostly at room temp, a few in the cold), then tested on an Anton Paar DMA4500/Alcolyzer classic

Focusing on ethanol increase as the endpoint, we know these to be relevant factors:

  • variety of hops (five different varieties)
  • presence of live yeast (true in all cases for these data)
  • temp.C = temperature during dryhopping (mostly warm, with a few in the cold)
  • amount of contact time between hops and beer (up to 7 weeks)

For more details on this dataset see the manuscript.
please send complaints and corrections to luke.chadwick@gmail.com! * Data are from the file “ujbc_a_1469081_sm5496.txt” (supplementary data from Jacob A. Kirkendall, Carter A. Mitchell & Lucas R. Chadwick (2018): The Freshening Power of Centennial Hops, Journal of the American Society of Brewing Chemists Volume 76, Issue 3, Pages 178-184 (2018) DOI: 10.1080/03610470.2018.1469081* available from https://www.tandfonline.com/doi/full/10.1080/03610470.2018.1469081?scroll=top&needAccess=true

load libraries

flash forward! scroll to the bottom and run bigchunk_tidytransform

import data

rm(list = ls()) # clear workspace
mydata <-read.csv("ujbc_a_1469081_sm5496.txt",stringsAsFactors = FALSE)  ## SPECIFY filename
mydata %>% filter(expt ==" 2A") %>% dplyr::select(sample_id)
`as_dictionary()` is soft-deprecated as of rlang 0.3.0.
Please use `as_data_pronoun()` instead
This warning is displayed once per session.`new_overscope()` is soft-deprecated as of rlang 0.2.0.
Please use `new_data_mask()` instead
This warning is displayed once per session.The `parent` argument of `new_data_mask()` is deprecated.
The parent of the data mask is determined from either:

  * The `env` argument of `eval_tidy()`
  * Quosure environments when applicable
This warning is displayed once per session.`overscope_clean()` is soft-deprecated as of rlang 0.2.0.
This warning is displayed once per session.
#dput(names(mydata))

manual add time zero to “days on hops” experiment!

Anton Paar data at time zero for kinetics experiment (triplicate samples run immediately after sample collection, while the remainder were being dry-hopped): Date time sample no. ABV ABW OE (P) Er Ea SG 20/20 RDF ADF sample_id 07/27/17 3:26 PM 03_ 6.68 5.2 15.95 6.08 3.74 1.01462 63.88 76.58 JK1-20-41 07/27/17 3:30 PM 04_ 6.68 5.2 15.95 6.08 3.73 1.01461 63.89 76.6 JK1-20-50 07/27/17 3:34 PM 05_ 6.68 5.2 15.95 6.08 3.74 1.01462 63.88 76.58 JK1-20-87

## fill in MISSING DATA :(  add time zero data for time series analysis)
## nested ifelse to add time zero plato values for each expt:
mydata<- mydata %>% mutate(., initial_plato = ifelse(expt==" 2A", 3.74, ifelse(expt==" 1A", 3.69, ifelse(expt==" 1B", 3.53, ifelse(expt==" 3B", 3.54, ifelse(expt==" 4B", 4.66, NA))))))
                                             
## add complete time-zero anton paar data for time series (expt2A)
## quadruple-up each triplicate time-zero measurement (one copy for each NH/DH & rouse/still combo)
## note:  this practice is of questionable statistical rigor.  In retrospect it would have been better (and a lot easier) to just run 12 samples through Anton on day0!
timezero <- mydata %>% filter(expt==" 2A") %>% arrange(special_group) %>% slice(1:12)
#data.entry(timezero)
timezero$Test_Date <- rep(c("7/27/2017","7/27/2017","7/27/2017"),4)
timezero$Test.time <- rep(c("3:26 PM", "3:30 PM", "3:34 PM"),4)
timezero$ABV <- 6.68
timezero$ABW <- 5.2
timezero$OE <- 15.95
timezero$Er <- 6.08
timezero$Ea <- rep(c(3.74, 3.73, 3.74),4)
timezero$SG <- rep(c(1.01462, 1.01461, 1.01462),4)
timezero$RDF <- rep(c(63.88, 63.89, 63.88),4)
timezero$ADF <- rep(c(76.58, 76.60, 76.58),4)
timezero$Calories <- rep(c(215.37,215.34,215.35),4)
timezero[timezero$hop=="DH",]$contact_days <-0
timezero[,c(28:31)] <- 0
mydata <- rbind(timezero,mydata)
write.csv(mydata, "quickie.csv", row.names = FALSE)
mydata<-read.csv("quickie.csv", stringsAsFactors = FALSE)
###  this is a routine for inspecting data ###
mydatatypes<- as.data.frame(cbind(sapply(mydata, class)))  ## table of datatypes
mydatatypes$headers <- rownames(mydatatypes)    ## convert rownames to values
na_count <-as.data.frame(sapply(mydata, function(y) sum(length(which(is.na(y)))))) ## count NAs by column
mydataOverview<-as.data.frame(cbind(na_count,mydatatypes))  ## table of NA counts and datatypes
names(mydataOverview) <- c("NA_count","Data_Class", "Header")
sum(is.na(mydata))  ## total count of NA values in entire sheet (231 in this case)
[1] 261
mydataOverview %>% filter(NA_count>0)    ## breakdown of NA values

tidy & transform

note: this is a base R +dplyr data wrangling exercise! in general it’s best to use lubridate package for date/timestamps!

test_df<- mydata # (original dataframe to be used for testing/illustration)
x_brew_date.POSIXct<- as.POSIXct(mydata$brew_date, format = "%m/%d/%Y") # as a standalone vector (USELESS here), or:
test_df$brew_date.POSIXct <-as.POSIXct(mydata$brew_date, format = "%m/%d/%Y")  # as a "new column" in our dataframe
datecols<- dput(names(dplyr::select(mydata, matches("date"))))  ## headers containing string "date" => c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date")
c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date"
)
## FORLOOP
for (icol in datecols) {
  newcol = paste0(icol,".POSIXct")
 # print(newcol)
  mydata[, newcol] = as.POSIXct(mydata[, icol],format = "%m/%d/%Y") ###  CREATE NEW columns with POSIXct   
#  mydata[, newcol] = as.POSIXct(as.numeric(mydata[, icol])  * (60*60*24), origin="1899-12-30") ###  microsoft times
}
mydata<- mydata %>% 
  mutate(dayofaddition = as.integer(as.numeric(difftime(dryhop_date.POSIXct,brew_date.POSIXct))),
         daysonhops = as.integer(as.numeric(difftime(Test_Date.POSIXct,dryhop_date.POSIXct))/(60*60*24)),
         hops_g_100mL = (mg_hops/volume_mL)/10,
         pounds_bbl = (mg_hops/volume_mL)*117/454
         )
mydata$OX<-grepl("OX", mydata$Hop_type)       ##  create logical "OX" column
mydata$rouse<-grepl("rouse", mydata$special_group)##  create logical column
mydata$Grind<-grepl("Grind", mydata$Hop_type)     ##  create logical column
mydata$Cone<-grepl("Cone", mydata$Hop_type)       ##  create logical column
mydata$harvest2014<-grepl("14", mydata$Hop_type)  ##  create logical column
mydata$harvest2015<-grepl("15", mydata$Hop_type)  ##  create logical column
mydata$harvest2017<-grepl("17", mydata$Hop_type)  ##  create logical column
## ifelse statement for harvestyear (if neither 2014 nor 2015 nor 2017, then 2016)
mydata$harvestYear <- ifelse(
  mydata$harvest2014==TRUE, 2014,
  ifelse(mydata$harvest2015==TRUE, 2015, 
         ifelse(mydata$harvest2017==TRUE, 2017, 2016)))
dput(levels(as.factor(mydata$harvestYear)))
c("2014", "2015", "2016", "2017")
## ifelse statement for form of hops (if neither cone nor ground nor NH, then pellet) 
mydata$form_of_hops <- ifelse(
  mydata$Cone==TRUE, "cone",
  ifelse(mydata$Grind==TRUE, "ground",
         ifelse(mydata$Hop_type=="NH", "NH", "pellet")))
dput(levels(as.factor(mydata$form_of_hops)))
c("cone", "ground", "NH", "pellet")
## ifelse statement for temperature greater or less than 10 
mydata$DH_temp <- ifelse(
  mydata$temp.C<10, "cold",
  ifelse(mydata$temp.C>10, "warm", "something else"))
dput(levels(as.factor(mydata$DH_temp)))
c("cold", "warm")
mydata$variety<-mydata$Hop_type
mydata$variety<- gsub("[0-9]+","", mydata$variety) ## remove all numbers
mydata$variety<- gsub("OX","", mydata$variety)     ## remove specific text
mydata$variety<- gsub("Grind","", mydata$variety)
mydata$variety<- gsub("Cone","", mydata$variety)
mydata$variety<- gsub(" ","", mydata$variety)      ## remove spaces
dput(levels(as.factor(mydata$variety)))
c("AMAR", "CASC", "CENT", "CIT", "NH", "SIM")
mydata<- mydata %>% mutate(EXPTnew=paste0("group",expt, substr(special_group, 1,2),as.character(rouse)))
# clean it up by removing "NA" and any spaces due to canarycode bug
mydata$EXPTnew <- gsub(" ","", mydata$EXPTnew)  ## remove any spaces
mydata$EXPTnew <- gsub("NA","", mydata$EXPTnew) ## remove "NA"
dput(levels(as.factor(mydata$expt)))
c(" 1A", " 1B", " 2A", " 3A", " 3B", " 4B")
dput(levels(as.factor(mydata$EXPTnew)))
c("group1AFALSE", "group1BFALSE", "group2A01FALSE", "group2A01TRUE", 
"group2A05FALSE", "group2A05TRUE", "group2A12FALSE", "group2A12TRUE", 
"group2A19FALSE", "group2A19TRUE", "group2A25FALSE", "group2A25TRUE", 
"group2A33FALSE", "group2A33TRUE", "group2A42FALSE", "group2A42TRUE", 
"group3AFALSE", "group3BFALSE", "group4BFALSE")

select and rearrange columns

(experimental factors on the left, then measurements, followed by calculations and then all date columns on the right):

mydata <- mydata %>% 
  dplyr::select(sample_id,expt, EXPTnew, hop, BINhop, variety, OX, harvestYear, form_of_hops,special_conditions, rouse, daysonhops, dayofaddition, DH_temp, temp.C, hops_g_100mL, pounds_bbl,
         ABV, ABW, OE, Er, Ea, SG, RDF, ADF, Calories, 
         dhop_day, contact_days, REF_NH, ABV_increase, 
         brew_date.POSIXct, sample_collection_date.POSIXct, dryhop_date.POSIXct,Test_Date.POSIXct, initial_plato)
## remove ".POSIXct" suffix.  Leaving it as-is will only add to confusion if/when these data are saved and re-imported (and become 'character' format!)
colnames(mydata) = gsub(".POSIXct", "", colnames(mydata))

compute mean NH (control) values

(NH = not dry-hopped)

## mean NH (control) values for each EXPTnew group
mean.control_NH<- mydata %>% 
  group_by(EXPTnew) %>%
  filter(hop=="NH") %>%
  summarise_at(vars(ABV, ABW, Ea, SG),funs(mean, n()))
## replace "_mean" with ".control_NH" in column names
colnames(mean.control_NH) = gsub("_mean", ".control_NH", colnames(mean.control_NH))

compute \(\Delta\) values (changes relative to un-dryhopped controls) using objects created above…

  • difference of each ABV,ABW,Ea, and SG data point from corresponding mean.control_NH values (unhopped samples in same EXPTnew group)
## first join our data with mean ABV for unhopped samples in given experiment (mean.control_NH; calculated above)
FPHcalc<- left_join(mydata, mean.control_NH, by="EXPTnew")
## calculate ABW_increase by subtracting each individual ABW measurement from respective mean.control_NH:
FPHcalc$delta.ABV <- FPHcalc$ABV - FPHcalc$ABV.control_NH
FPHcalc$delta.ABW <- FPHcalc$ABW - FPHcalc$ABW.control_NH
FPHcalc$delta.plato <- FPHcalc$Ea - FPHcalc$Ea.control_NH
FPHcalc$delta.SG <- FPHcalc$SG - FPHcalc$SG.control_NH
## the control samples have served their purpose, now remove them from dataset. The following calculations are only meaningful for dry-hopped samples.  
FPHcalc<- FPHcalc %>% filter(hop=="DH")

calculate corresponding CO2 production

  • following Bamforth (describing Balling equation) “…more realistically, the ethanol yield is more like 0.46 g and carbon dioxide 0.44 g from 1 g sugar” (p. 137 in Brewing Materials and Processes: A Practical Approach to Beer Excellence, Edited by Charles Bamforth Academic Press, 2016)
FPHcalc$calcCO2_increase <- FPHcalc$delta.ABW*(0.44/0.46)
##convert calcCO2_increase (in g/100mL) to calculated CO2 volumes added
## g/L = 10* g/100mL
## The conversion factor from volumes of CO2 to CO2 by weight (g/L) is 1.96. For example: 2.5 volumes x 1.96 = 4.9 g/l.
FPHcalc$calcCO2vols_increase <- FPHcalc$calcCO2_increase*10/1.96

define “FPH” as amount produced per amount dry-hops added (all in g/100mL):

##  FPH = Fold Production due to Hops (fold-increase by mass: amount of given endpoint relative to amount of hops added)
##  
FPHcalc$FPH_EtOH = FPHcalc$delta.ABW/FPHcalc$hops_g_100mL
FPHcalc$FPH_CO2 = FPHcalc$calcCO2_increase/FPHcalc$hops_g_100mL
FPHcalc$FPH_plato = FPHcalc$delta.plato/FPHcalc$hops_g_100mL
## replacing time-zero time values with a very small number (rather than exactly zero) will prevent issues with analysis of nonlinear models
FPHcalc[FPHcalc$daysonhops==0,]$daysonhops <- 0.01
## and save the transformed data to csv:
write.csv(FPHcalc,"FPHcalc.csv", row.names = FALSE)
FPHcalc <- read.csv("FPHcalc.csv", stringsAsFactors = TRUE)
df<- FPHcalc %>% group_by(EXPTnew) %>%
  summarise_at(vars(hops_g_100mL,pounds_bbl,delta.ABV, delta.ABW, delta.plato, FPH_plato, FPH_EtOH, FPH_CO2, calcCO2vols_increase),funs(round(mean(.), 2))) %>%
  arrange(desc(FPH_EtOH)) 
df

intermission?

visualize

vector~vector*vector plots

df <-FPHcalc
p1<- ggplot(df, aes(y=FPH_EtOH,x=OE, color=contact_days)) + geom_point(size=2)
p2<- ggplot(df, aes(y=FPH_EtOH,x=ADF, color=contact_days)) + geom_point(size=2)
p3<- ggplot(df, aes(y=FPH_EtOH,x=ABW, color=contact_days)) + geom_point(size=2)
p4<- ggplot(df, aes(y=FPH_EtOH,x=Ea, color=contact_days)) + geom_point(size=2)
grid.arrange(p1, p2, p3, p4, ncol = 2)

x~y*(4 vectors) by color

df <- FPHcalc
x<- df$Ea
y<- df$ABW    #FPH_EtOH
p1<- ggplot(df, aes(x,y, color=form_of_hops)) + geom_point(size=2)
p2<- ggplot(df, aes(x,y, color=brew_date)) + geom_point(size=2)
p3<- ggplot(df, aes(x,y, color=rouse)) + geom_point(size=2)
p4<- ggplot(df, aes(x,y, color=pounds_bbl)) + geom_point(size=2)
grid.arrange(p1, p2, p3, p4, ncol = 2)

3D plots with library(plotly)

# interactive 3D plots with library(plotly)
plot_ly(data = df, z = df$PLATO.hopdrop, x = ~df$daysonhops, y =  df$ABW)
No trace type specified:
  Based on info supplied, a 'scatter3d' trace seems appropriate.
  Read more about this trace type -> https://plot.ly/r/reference/#scatter3d
No scatter3d mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
No trace type specified:
  Based on info supplied, a 'scatter3d' trace seems appropriate.
  Read more about this trace type -> https://plot.ly/r/reference/#scatter3d
No scatter3d mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode

Amunategui “Using Correlations To Understand Your Data”

mydata<-FPHcalc %>% dplyr::select(expt,variety,form_of_hops,pounds_bbl,special_conditions,rouse,daysonhops,DH_temp,brew_date,ABW,SG,OE,ADF,RDF,calcCO2vols_increase,FPH_CO2, FPH_EtOH)  ## last one is 100% in j
## note use of "dplyr::select" because one of these packages is conflicting with dplyr commands :()
## following  Manuel Amunategui  https://www.youtube.com/watch?v=igPQ-pI8Bjo
## Using Correlations To Understand Your Data: Machine Learning With R 
##functions for flattenSquareMatrix
cor.prob <- function (X, dfr=nrow(X) -2) {
  R<- cor(X, use="pairwise.complete.obs")
  above<- row(R) < col(R)
  r2 <- R[above]^2
  Fstat<- r2 * dfr/(1-r2)
  R[above] <- 1- pf(Fstat, 1, dfr)
  R[row(R) == col(R)] <- NA
  R
}
flattenSquareMatrix <- function(m) {
  if( (class(m) != "matrix") | (nrow(m)!=ncol(m))) stop("Must be a square matrix.")
  if(!identical(rownames(m), colnames(m))) stop("Row and column names must be equal.")
  ut <- upper.tri(m)
  data.frame(i = rownames(m)[row(m)[ut]],
             j = rownames(m)[col(m)[ut]],
             cor=t(m)[ut],
             p=m[ut])
}
## library(caret) to dummify everything (turn all characters&factors into columns;  ignores numbers and integers)
dmy<- dummyVars(" ~ .",data = mydata)
mydummifieddata<- data.frame(predict(dmy, newdata = mydata))
corMat = cor(mydummifieddata)
corMasterList<- flattenSquareMatrix(cor.prob(mydummifieddata)) ## list of all correlations
## order by strength of correlation
corlist<- corMasterList %>% arrange(-abs(corMasterList$cor)) 
write.csv(corlist,paste0("FLAT correlation matrix_.csv"))
corlist <- corlist %>% dplyr::filter(j=="FPH_EtOH")   ## filter specific endpoint
head(corlist,50)

pairs.panels correlation matrix from library(psych)

## specify interesting variables:  
interestingvariables<-c("ABW", "pounds_bbl", "daysonhops", "calcCO2vols_increase") 
pairs.panels(mydummifieddata[c(interestingvariables, "FPH_EtOH")])

model

Observing the impacts of dryhopping in the presence of live yeast has led many brewing professionals to understand that FPH is a function of many of the variables above including hop variety,form_of_hops,harvestYear,OX,DH_temp,daysonhops,rouse,pounds_bbl…. Many have intuitively created a model in their heads (without necessarily thinking of it as such) and skillfully adjust process when necessary to account for this phenomenon. In linear modeling, our function will take on the form: \(FPH = intercept + \beta_{1}X_{1} + \beta_{2}X_{2} + ... + \beta_{n}X_{n}\) where \(\beta\) values are what we’re attempting to derive in this modeling exercise, and X values are (collectively) a particular set of conditions.

ActionItem: add link to modeling overview

linear models 1 (create models)

df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(df$SG~df$OE)
abline(lm1)
plot(df$SG~df$ADF)
abline(lm2)
plot(df$SG~df$ABW)
abline(lm3)
plot(df$SG~df$Ea)
abline(lm4)

linear models 2 (view residual plots)

df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(lm1$residuals)
plot(lm2$residuals)
plot(lm3$residuals)
plot(lm4$residuals)

#summary(lm4)

compare linear models using memisc package

df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
mtable1234 <- mtable("Model 1"=lm1,"Model 2"=lm2,"Model 3"=lm3, "Model 4"=lm4,
                    summary.stats=c("sigma","R-squared","F","p","N"),show.eqnames=T)
mtable1234b <- relabel(mtable1234,
                      "(Intercept)" = "Constant",
                      x1 = "OE = Original Extract (g/100mL)",
                      x2 = "ADF = Apparent Degree of Fermentation (%)",
                      x3 = "ABW = Ethanol (w/w)",
                      x4 = "Er = Residual Extract (g/100mL)"
                      )
mtable1234

Calls:
Model 1: lm(formula = df$SG ~ df$OE)
Model 2: lm(formula = df$SG ~ df$ADF)
Model 3: lm(formula = df$SG ~ df$ABW)
Model 4: lm(formula = df$SG ~ df$Ea)

======================================================================
                Model 1      Model 2       Model 3       Model 4      
              ----------- ------------- ------------ ---------------   
                 df$SG        df$SG         df$SG         df$SG       
----------------------------------------------------------------------
  (Intercept)    1.071***      1.063***     1.056***        1.000***  
                (0.020)       (0.000)      (0.001)         (0.000)    
  df$OE         -0.004**                                              
                (0.001)                                               
  df$ADF                      -0.001***                               
                              (0.000)                                 
  df$ABW                                   -0.008***                  
                                           (0.000)                    
  df$Ea                                                     0.004***  
                                                           (0.000)    
----------------------------------------------------------------------
  sigma          0.002         0.000        0.001           0.000     
  R-squared      0.066         0.997        0.934           1.000     
  F              8.553     48038.147     1700.301     3532408.297     
  p              0.004         0.000        0.000           0.000     
  N            123           123          123             123         
======================================================================
#show_html(mtable1234b)

compare more linear models

df <- FPHcalc
lm1<-lm(FPH_EtOH~daysonhops, data=df)
lm2<-lm(FPH_EtOH~daysonhops*form_of_hops, data=df)
lm3<-lm(FPH_EtOH~daysonhops*rouse, data=df)
lm4<-lm(FPH_EtOH~daysonhops*form_of_hops*rouse, data=df)
mtable1234 <- mtable("Model 1"=lm1,"Model 2"=lm2,"Model 3"=lm3, "Model 4"=lm4,
                    summary.stats=c("sigma","R-squared","F","p","N"),show.eqnames=T)
mtable1234b <- relabel(mtable1234,
                      "(Intercept)" = "Constant",
                      SG = "Specific Gravity",
                      ABW = "ABW = Ethanol (w/w)",
                      Er = "Er = Residual Extract (g/100mL)"
                      )
mtable1234

Calls:
Model 1: lm(formula = FPH_EtOH ~ daysonhops, data = df)
Model 2: lm(formula = FPH_EtOH ~ daysonhops * form_of_hops, data = df)
Model 3: lm(formula = FPH_EtOH ~ daysonhops * rouse, data = df)
Model 4: lm(formula = FPH_EtOH ~ daysonhops * form_of_hops * rouse, data = df)

=============================================================================
                              Model 1     Model 2     Model 3     Model 4    
                            ----------- ----------- ----------- -----------   
                              FPH_EtOH    FPH_EtOH    FPH_EtOH    FPH_EtOH   
-----------------------------------------------------------------------------
  (Intercept)                  0.498***    0.306       0.537***    0.319     
                              (0.044)     (0.197)     (0.049)     (0.197)    
  daysonhops                   0.044***    0.044***    0.041***    0.041***  
                              (0.003)     (0.003)     (0.004)     (0.004)    
  form_of_hops: ground/cone                0.849**                 0.849**   
                                          (0.278)                 (0.277)    
  form_of_hops: pellet/cone                0.174                   0.199     
                                          (0.200)                 (0.199)    
  rouse                                               -0.227      -0.208     
                                                      (0.122)     (0.118)    
  daysonhops x rouseTRUE                               0.010       0.009     
                                                      (0.006)     (0.006)    
-----------------------------------------------------------------------------
  sigma                        0.355       0.340       0.352       0.339     
  R-squared                    0.634       0.669       0.645       0.678     
  F                          210.023      80.145      72.038      49.193     
  p                            0.000       0.000       0.000       0.000     
  N                          123         123         123         123         
=============================================================================
#show_html(mtable1234b)

The R Book (Crawley) Table 20.1: nonlinear functions useful in biology

Table 20.1. Useful non-linear functions EXPANDED:

Function Class name equation example code example applications
Asymptotic functions Michaelis–Menten \(y =\frac{ax}{1+bx}\) nls(bone~aage/(1+bage),start=list(a=8,b=0.08))) enzyme reactions
nls(rate~SSmicmen(conc,a,b)) tbd
2-parameter asymptotic exponential \(y = a(1 − e^{−bx} )\) nls(bone~a(1-exp(-cage)),start=list(a=120,c=0.064)) tbd
3-parameter asymptotic exponential \(y = a − be^{−cx}\) nls(bone~a-bexp(-cage),start=list(a=120,b=110,c=0.064)) tbd
nls(bone~SSasymp(age,a,b,c)) tbd
nls(density ~ SSlogis(log(concentration), a, b, c)) tbd
S-shaped functions 2-parameter logistic \(y = \frac{e^{a+bx}}{1 + e^{a+bx}}\) tbd
3-parameter logistic \(y = \frac{a}{1 + be^{−cx}}\) tbd
4-parameter logistic \(y = a + \frac{b-a}{1 + e^{(c−x)/d}}\) nls(weight~SSfpl(Time, a, b, c, d)) tbd
Weibull \(y = a − be^{−(cx^d)}\) nls(weight ~ SSweibull(time, Asym, Drop, lrc, pwr)) tbd
Gompertz \(y = ae^{−be^{−cx}}\) tbd
Humped curves Ricker curve \(y = axe^{−bx}\) tbd
First-order compartment \(y = k exp(−exp(a)x) − exp(−exp(b)x)\) nls(conc~SSfol(Dose, Time, a, b, c)) tbd
Bell-shaped \(y = a exp(−ABS(bx)^2)\) tbd
Biexponential \(y = ae^{bx} − ce^{−dx}\) tbd

Michaelis-Menton model (Crawley p. 741)

\(y =\frac{ax}{1+bx}\)

expt2 <- FPHcalc %>% dplyr::filter(expt==" 2A") %>% dplyr::select(FPH_EtOH, rouse, daysonhops,brew_date, pounds_bbl,form_of_hops,ABW.control_NH,dayofaddition)
mymodel <- nls(FPH_EtOH~a*daysonhops/(1+b*daysonhops), data=expt2,start=list(a=8, b=0.08))
summary(mymodel)

Formula: FPH_EtOH ~ a * daysonhops/(1 + b * daysonhops)

Parameters:
  Estimate Std. Error t value Pr(>|t|)    
a 0.186159   0.010109   18.41  < 2e-16 ***
b 0.067820   0.005728   11.84 1.45e-15 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.09307 on 46 degrees of freedom

Number of iterations to convergence: 6 
Achieved convergence tolerance: 1.686e-06
x <- seq(0,50,0.1)
yv <- predict(mymodel, list(daysonhops=x))
{plot(expt2$daysonhops, expt2$FPH_EtOH, pch=21, col="purple", bg="green")
  lines(x,yv,col="blue")}

So using the Michaelis-Menten equation as our model, the relationship between hop-induced ethanol production (FPH_EtOH) and hop contact time (daysonhops) would be expressed as: \(FPH_EtOH = \frac{0.183572*daysonhops}{1+0.066388*daysonhops}\)

nonlinear models of hop-induced Plato drop over time with library(drc)

df <- FPHcalc %>% filter(expt==" 2A") 
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- df$daysonhops
y <- df$PLATO.hopdrop
group<- as.factor(df$special_conditions)   #rouse
cols <- as.numeric(group)
legend.cols <- as.numeric(as.factor(levels(group)))
## Fitting models using function drm from library(drc). see <http://rstats4ag.org/dose-response-curves.html> for overview and <https://www.rdocumentation.org/packages/drc/> for list of models (fct values) and other functions available in drc
## create models
m.LL.3<-drm(y ~x,group, fct = LL.3()) #3-parameter logistic (lower limit at 0)
NaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs produced
#m.LL.3u<-drm(y ~x, fct = LL.3u()) #3-parameter logistic (upper limit at 1)
m.LL.4<-drm(y ~x,group, fct = LL.4()) #4-parameter log-logistic  
# from ??LL.4:  f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\log(e)))} or in another parameterisation (converting the term \log(e) into a parameter) f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\tilde{e}))}  
m.L.4 <-drm(y ~x,group, fct = L.4()) #changing the fct = LL.4() to fct = L.4() allows plotting on a log10 scale
m.LL2.4<-drm(y ~x,group, fct = LL2.4()) #4-parameter log-logistic with log(e) rather than e as a parameter  
m.LL2.5<-drm(y ~x,group, fct = LL2.5()) #Generalised log-logistic
# from ??LL.2.5:   f(x) = c + \frac{d-c}{(1+\exp(b(\log(x)-e)))^f}
m.LL.5<-drm(y ~x,group, fct = LL.5()) #5-parameter logistic  
m.W1.4<-drm(y ~x,group, fct = W1.4()) #4-parameter Weibull1  
NaNs produced
m.W2.4<-drm(y ~x,group, fct = W2.4()) #4-parameter Weibull2  
m.BC.5<-drm(y ~x,group, fct = BC.5()) #5-parameter Brain-Cousens (hormesis)
m.AR.3<-drm(y ~x,group, fct = AR.3()) #3-parameter Shifted asymptotic regression
NaNs producedNaNs produced
#m.MM.2<-drm(y ~x,group, fct = MM.2()) #2-parameter Michaelis-Menten
m.MM.3<-drm(y ~x,group, fct = MM.3()) #3-parameter Michaelis-Menten
NaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs produced
##plot
#par(mfrow = c(1,2), oma = c(0, 0, 0, 0))
{plot(x , y, col=cols, main="raw data")
legend("topright", legend=levels(group), pch=16, col=legend.cols)}

plot(m.LL.3, type='all',col=cols, main="LL.3 (lower limit at 0)")

plot(m.LL.4, type='all',col=cols, main="LL.4 four-parameter log-logistic")

plot(m.L.4, type='all',col=cols, main="L.4")

plot(m.LL.5, type='all',col=cols, main="LL.5")

plot(m.LL2.4, type='all',col=cols, main="LL2.4 four-parameter log-logistic")

plot(m.LL2.5, type='all',col=cols, main="Generalised log-logistic")

plot(m.W1.4, type='all',col=cols, main="Weibull1")

plot(m.W2.4, type='all',col=cols, main="Weibull2")

plot(m.BC.5, type='all',col=cols, main="Brain-Cousens (hormesis)")

plot(m.AR.3, type='all',col=cols, main="Shifted asymptotic regression")

#plot(m.MM.2, type='all',col=cols, main="2-parameter Michaelis-Menten")
plot(m.MM.3, type='all',col=cols, main="3-parameter Michaelis-Menten")

Speers2003: Non‐Linear Modelling of Industrial Brewing Fermentations

R. Alex Speers, Peter Rogers, Bruce Smith J. Inst. Brew. 109(3), 229–235, 2003 https://doi.org/10.1002/j.2050-0416.2003.tb00163.x Free Access from the Institute of Brewing! https://onlinelibrary.wiley.com/doi/10.1002/j.2050-0416.2003.tb00163.x

Following Speers2003, the relationship between plato and time in primary fermentations is well modeled by a four parameter logistic model:

PLATO = PINF + (P_D / (1+ (EXP(-B*(TIME-M)))))

or

\(PLATO = P_{inf}+ \frac{P_D}{1+e^{-B(t-M)}}\)

where

  • t = time into fermentation
  • P_D = change in Plato during fermentation (OE - P_{inf})
  • B ~ maximum fermentation rate
  • M = fermenation midpoint
  • P_inf = final gravity

so then for our case

\(PLATO = P_{inf}+ \frac{OE-P_{inf}}{1+e^{-F_{max}(daysonhops-M)}}\)

PLATO = PINF + (P0 / (1+ (EXP(B*(HOURS-M)))))

MacIntosh2016: An Examination of Substrate and Product Kinetics During Brewing Fermentations

Andrew J. MacIntosh, Maria Josey, R. Alex Speers J. Am. Soc. Brew. Chem. 74(4), 250-257, 2016

“the five-parameter method was selected over the standard four-parameter logistic detailed in ASBC Method Yeast- 14 because it yielded a superior fit when assessed with an extra sum-of-squares F test. We further note that it is only statistically advantageous to apply the five-parameter model when many data points are available (72 from this experiment as opposed to 10 when using Yeast-14).”

MacIntosh: \(P_{(t)} =\frac{P_i - P_e}{(1+s * e^{-B(t-M)})^{1/s}}\)

library(drc) getMeanFunctions()

  • drc LL.4: \(f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\tilde{e}))}\) #4param logistic
  • drc LL.5: \(f(x) = c + \frac{d-c}{(1+\exp(b(\log(x)-e)))^f}\) #5param logistic
  • drc G.4 \(f(x) = c + (d-c)(\exp(-\exp(b(x-e))))\) #4param Gompertz
  • drc W1.4 \(f(x) = c + (d-c) \exp(-\exp(b(\log(x)-\log(e))))\) #4param Weibull1
  • drc W2.4 \(f(x) = c + (d-c) (1 - \exp(-\exp(b(\log(x)-\log(e)))))\) #4param Weibull2

multilevel logistic models with library(drc)

df <- FPHcalc %>% filter(expt==" 2A") 
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- df$daysonhops
y <- df$PLATO.hopdrop
mylevels<- as.factor(df$special_conditions)  #rouse
cols <- as.numeric(mylevels)
legend.cols <- as.numeric(as.factor(levels(mylevels)))
## create models
#m.LL.4<-drm(y ~x,mylevels, fct = LL.4(names = c("Slope", "Lower", "Upper", "Midpoint or ED50"))) #4-parameter log-logistic (general parameters)
#m.LL.4<-drm(y ~x,mylevels, fct = LL.4(names = c("Slope", "Lower", "Upper", "ED50"))) #4-parameter log-logistic (Dose-Response parameters)
m.LL.4<-drm(y ~x,mylevels, fct = LL.4(names = c("F_max", "P_inf", "OE", "M"))) #4-parameter log-logistic  
m.L.4 <-drm(y ~x,mylevels, fct = L.4()) #changing the fct = LL.4() to fct = L.4() allows plotting on a log10 scale
m.LL2.4<-drm(y ~x,mylevels, fct = LL2.4()) #4-parameter log-logistic with log(e) rather than e as a parameter  
m.LL2.5<-drm(y ~x,mylevels, fct = LL2.5()) #Generalised log-logistic
m.LL.5<-drm(y ~x,mylevels, fct = LL.5()) #5-parameter logistic  
mymodel <- m.LL.4
confint(mymodel)
                2.5 %    97.5 %
F_max:rouse  1.186734  1.597836
F_max:still  1.537194  2.063586
P_inf:rouse  1.653949  1.963275
P_inf:still  2.079132  2.219179
OE:rouse     3.710521  3.787753
OE:still     3.714969  3.786819
M:rouse     10.616221 13.931883
M:still      7.316189  8.600129
{plot(mymodel,log="",  broken = TRUE, bcontrol = list(style = "slash"), col = c(2,6,3,23,56), main = "impact of mylevels on estimated model paramaters")
legend(34, 3.7, legend=c("roused","still"), fill=c("red", "pink"), title="mylevels")}

mymodel

A 'drc' model.

Call:
drm(formula = y ~ x, curveid = mylevels, fct = LL.4(names = c("F_max",     "P_inf", "OE", "M")))

Coefficients:
F_max:rouse  F_max:still  P_inf:rouse  P_inf:still     OE:rouse     OE:still  
      1.392        1.800        1.809        2.149        3.749        3.751  
    M:rouse      M:still  
     12.274        7.958  
summary(mymodel)

Model fitted: Log-logistic (ED50 as parameter) (4 parms)

Parameter estimates:

             Estimate Std. Error t-value   p-value    
F_max:rouse  1.392285   0.101704  13.690 < 2.2e-16 ***
F_max:still  1.800390   0.130226  13.825 < 2.2e-16 ***
P_inf:rouse  1.808612   0.076525  23.634 < 2.2e-16 ***
P_inf:still  2.149156   0.034647  62.030 < 2.2e-16 ***
OE:rouse     3.749137   0.019107 196.219 < 2.2e-16 ***
OE:still     3.750894   0.017775 211.018 < 2.2e-16 ***
M:rouse     12.274052   0.820272  14.963 < 2.2e-16 ***
M:still      7.958159   0.317638  25.054 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error:

 0.03875737 (40 degrees of freedom)

Each level (rouse and still) has a unique curve shape and therefore different parameters for a best-fit log-logistic model. Now that we know this, it is practical to address each set of conditions separately, and based on the LL.4 model above we have:

for rouse = TRUE:

\(PLATO_{roused} = P_{inf}+ \frac{OE-P_{inf}}{1+e^{-F_{max}(daysonhops-M)}}\)

or

\(PLATO_{roused} = 1.809 + \frac{1.94}{1+e^{-1.3925(daysonhops-12.272)}}\)

and

\(PLATO_{still} = 2.149 + \frac{1.60}{1+e^{-1.8004(daysonhops-7.958)}}\)

Plotting and analysis options for multilevel models are limited, so let’s move forward with a single set of conditions and a ‘simple’ single-level logistic model (expt=2a, rouse=TRUE).

unilevel logistic model with confidence intervals

df <- FPHcalc %>% filter(expt==" 2A" & rouse==TRUE) 
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- as.numeric(df$daysonhops)
y <- df$PLATO.hopdrop
## create model
mymodel<-drm(y ~x, fct = LL.4()) #4-parameter logistic with library(drc)
plot(mymodel, main = "default")

plot(mymodel, log="", main = "non-logarithmic x-axis")

# create prediction intervals
newdata = data.frame(y = y, x = x)
conf95 <- as.data.frame(predict(mymodel, newdata,interval = "confidence", level = 0.95))
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
conf95$x <- x
pred95 <- as.data.frame(predict(mymodel, newdata, interval = "prediction", level = 0.95))
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
pred95$x <- x
pred999 <- as.data.frame(predict(mymodel, newdata, interval = "prediction", level = 0.999))
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
pred999$x <- x
# Asymmetric ribbons based on prediction intervals
#newdata <- data.frame(y = c(3.5, 3, 2.9, 2.5,1.8,1.7), x = c(1,5,10,20,30,40))
#newdata <- tidyfermi %>% filter(batch = SELECTBATCH) %>% select(days,plato) 
qplot(data=pred95, x=x, y=Prediction, ymin=Lower, ymax=Prediction, geom="ribbon", fill=I("red"), alpha=I(0.2)) +
geom_ribbon(data=pred95, aes(x=x, ymin=Prediction, ymax=Upper), fill=I("blue"), alpha=I(0.2)) +
geom_line(data=pred95, aes(x=x, y=Prediction), color=I("green"), lwd=1)+
geom_point(data=newdata, aes(x=x, y=y, ymin=NULL, ymax=NULL), size=1, col="blue")+
 ylab("y")

# Visualise intervals
# based on Maurits Evers (https://stackoverflow.com/questions/49444489/nonlinear-regression-prediction-in-r?rq=1)
data.frame(x=x, y=y) %>% ggplot(aes(x, y)) +
geom_point() +
geom_line(data = pred95, aes(x = x, y = Prediction)) +
geom_ribbon(data = pred999, aes(x = x, ymin = Lower, ymax = Upper),fill=I("pink"),alpha = 0.4)+
geom_ribbon(data = pred95, aes(x = x, ymin = Lower, ymax = Upper),fill=I("green"),alpha = 0.4)+
geom_ribbon(data = conf95, aes(x = x, ymin = Lower, ymax = Upper),fill=I("purple"),alpha = 0.4);

Note: If you compare the multi-level model parameter estimates for rouse=TRUE with those for unilevel model based on rouse=TRUE subset, the results are quite similar, but not identical!

plot residuals of nonlinear models generated in library(drc)

par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(m.MM.3$predres)
plot(m.LL.3$predres)
plot(m.LL.4$predres)
plot(m.LL.5$predres)

summary

For cases where 2016 Centennial hops are added to this particular base beer (sampled into 12oz bottles) near the end of primary fermentation at room temperature and bottles remain untouched (still), the 4-parameter logistic equation with the following sets of parameters is a reasonable model of the subsequent hop-induced plato drop:

\(PLATO_{still} = 2.149 + \frac{1.60}{1+e^{-1.8004(daysonhops-7.958)}}\)

The following 4-parameter logistic equation is a reasonable model for cases identical to the above, but where bottles are roused once daily:

\(PLATO_{roused} = 1.809 + \frac{1.94}{1+e^{-1.3925(daysonhops-12.272)}}\)

Whereas the residual sum of squares for the 5-parameter logistic models was similar to those obtained for the 4-parameter equation, a visual inspection reveals that the 4-parameter equation is probably superior, in that the 4-parameter fit apparently does not [falsely?] predict a long lag time in the case of unroused samples.

Only a single variable (rousing vs. still) among several hundred variables encompassing raw materials, machines, people and process resulted in quite different estimated parameters for a 4-parameter logistic model. These results highlight the fact that all process variables that significantly impact the endpoints we wish to control must themselves be defined, controlled and/or documented if models such as these are to be used to characterize the process in question.

bigchunk_tidytransform

mydata <-read.csv("ujbc_a_1469081_sm5496.txt",stringsAsFactors = FALSE)  ## SPECIFY filename
## fill in MISSING DATA :(  add time zero data for time series analysis)
## nested ifelse to add time zero plato values for each expt:
mydata<- mydata %>% mutate(., initial_plato = ifelse(expt==" 2A", 3.74, ifelse(expt==" 1A", 3.69, ifelse(expt==" 1B", 3.53, ifelse(expt==" 3B", 3.54, ifelse(expt==" 4B", 4.66, NA))))))
                                             
## add complete time-zero anton paar data for time series (expt2A)
## quadruple-up each triplicate time-zero measurement (one copy for each NH/DH & rouse/still combo)
## note:  this practice is of questionable statistical rigor.  In retrospect it would have been better (and a lot easier) to just run 12 samples through Anton on day0!
timezero <- mydata %>% filter(expt==" 2A") %>% arrange(special_group) %>% slice(1:12)
#data.entry(timezero)
timezero$Test_Date <- rep(c("7/27/2017","7/27/2017","7/27/2017"),4)
timezero$Test.time <- rep(c("3:26 PM", "3:30 PM", "3:34 PM"),4)
timezero$ABV <- 6.68
timezero$ABW <- 5.2
timezero$OE <- 15.95
timezero$Er <- 6.08
timezero$Ea <- rep(c(3.74, 3.73, 3.74),4)
timezero$SG <- rep(c(1.01462, 1.01461, 1.01462),4)
timezero$RDF <- rep(c(63.88, 63.89, 63.88),4)
timezero$ADF <- rep(c(76.58, 76.60, 76.58),4)
timezero$Calories <- rep(c(215.37,215.34,215.35),4)
timezero[timezero$hop=="DH",]$contact_days <-0
timezero[,c(28:31)] <- 0
mydata <- rbind(timezero,mydata)
write.csv(mydata, "quickie.csv", row.names = FALSE)
mydata<-read.csv("quickie.csv", stringsAsFactors = FALSE)
###  this is a routine for inspecting data ###
mydatatypes<- as.data.frame(cbind(sapply(mydata, class)))  ## table of datatypes
mydatatypes$headers <- rownames(mydatatypes)    ## convert rownames to values
na_count <-as.data.frame(sapply(mydata, function(y) sum(length(which(is.na(y)))))) ## count NAs by column
mydataOverview<-as.data.frame(cbind(na_count,mydatatypes))  ## table of NA counts and datatypes
names(mydataOverview) <- c("NA_count","Data_Class", "Header")
sum(is.na(mydata))  ## total count of NA values in entire sheet (231 in this case)
[1] 261
mydataOverview %>% filter(NA_count>0)    ## breakdown of NA values
datecols<- dput(names(dplyr::select(mydata, matches("date"))))  ## headers containing string "date" => c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date")
c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date"
)
## FORLOOP
for (icol in datecols) {
  newcol = paste0(icol,".POSIXct")
 # print(newcol)
  mydata[, newcol] = as.POSIXct(mydata[, icol],format = "%m/%d/%Y") ###  CREATE NEW columns with POSIXct   
#  mydata[, newcol] = as.POSIXct(as.numeric(mydata[, icol])  * (60*60*24), origin="1899-12-30") ###  microsoft times
}
## create dayofaddition, daysonhops, hops_g_100mL, pounds_bbl variables with dplyr "mutate"
mydata<- mydata %>% 
  mutate(dayofaddition = as.integer(as.numeric(difftime(dryhop_date.POSIXct,brew_date.POSIXct))),
         daysonhops = as.integer(as.numeric(difftime(Test_Date.POSIXct,dryhop_date.POSIXct))/(60*60*24)),
         hops_g_100mL = (mg_hops/volume_mL)/10,
         pounds_bbl = (mg_hops/volume_mL)*117/454
         )
         
mydata$OX<-grepl("OX", mydata$Hop_type)       ##  create logical "OX" column
mydata$rouse<-grepl("rouse", mydata$special_group)##  create logical column
mydata$Grind<-grepl("Grind", mydata$Hop_type)     ##  create logical column
mydata$Cone<-grepl("Cone", mydata$Hop_type)       ##  create logical column
mydata$harvest2014<-grepl("14", mydata$Hop_type)  ##  create logical column
mydata$harvest2015<-grepl("15", mydata$Hop_type)  ##  create logical column
mydata$harvest2017<-grepl("17", mydata$Hop_type)  ##  create logical column
## ifelse statement for harvestyear (if neither 2014 nor 2015 nor 2017, then 2016)
mydata$harvestYear <- ifelse(
  mydata$harvest2014==TRUE, 2014,
  ifelse(mydata$harvest2015==TRUE, 2015, 
         ifelse(mydata$harvest2017==TRUE, 2017, 2016)))
## ifelse statement for form of hops (if neither cone nor ground nor NH, then pellet) 
mydata$form_of_hops <- ifelse(
  mydata$Cone==TRUE, "cone",
  ifelse(mydata$Grind==TRUE, "ground",
         ifelse(mydata$Hop_type=="NH", "NH", "pellet")))
## ifelse statement for temperature greater or less than 10 
mydata$DH_temp <- ifelse(
  mydata$temp.C<10, "cold",
  ifelse(mydata$temp.C>10, "warm", "something else"))
mydata$variety<-mydata$Hop_type
mydata$variety<- gsub("[0-9]+","", mydata$variety) ## remove all numbers
mydata$variety<- gsub("OX","", mydata$variety)     ## remove specific text
mydata$variety<- gsub("Grind","", mydata$variety)
mydata$variety<- gsub("Cone","", mydata$variety)
mydata$variety<- gsub(" ","", mydata$variety)      ## remove spaces
mydata<- mydata %>% mutate(EXPTnew=paste0("group",expt, substr(special_group, 1,2),as.character(rouse)))
# clean it up by removing "NA" and any spaces due to canarycode bug
mydata$EXPTnew <- gsub(" ","", mydata$EXPTnew)  ## remove any spaces
mydata$EXPTnew <- gsub("NA","", mydata$EXPTnew) ## remove "NA"
## select and rearrange columns 
mydata <- mydata %>% 
  dplyr::select(sample_id,expt, EXPTnew, hop, BINhop, variety, OX, harvestYear, form_of_hops,special_conditions, rouse, daysonhops, dayofaddition, DH_temp, temp.C, hops_g_100mL, pounds_bbl,
         ABV, ABW, OE, Er, Ea, SG, RDF, ADF, Calories, 
         dhop_day, contact_days, REF_NH, ABV_increase, 
         brew_date.POSIXct, sample_collection_date.POSIXct, dryhop_date.POSIXct,Test_Date.POSIXct, initial_plato)
## remove ".POSIXct" suffix.  Leaving it as-is will only add to confusion if/when these data are saved and re-imported (and become 'character' format!)
colnames(mydata) = gsub(".POSIXct", "", colnames(mydata))
##compute mean NH (control) values 
## mean NH (control) values for each EXPTnew group
mean.control_NH<- mydata %>% 
  group_by(EXPTnew) %>%
  filter(hop=="NH") %>%
  summarise_at(vars(ABV, ABW, Ea, SG),funs(mean, n()))
## replace "_mean" with ".control_NH" in column names
colnames(mean.control_NH) = gsub("_mean", ".control_NH", colnames(mean.control_NH))
##compute $\Delta$ values (changes relative to un-dryhopped controls) using objects created above...
## first join our data with mean ABV for unhopped samples in given experiment (mean.control_NH; calculated above)
FPHcalc<- left_join(mydata, mean.control_NH, by="EXPTnew")
## calculate ABW_increase by subtracting each individual ABW measurement from respective mean.control_NH:
FPHcalc$delta.ABV <- FPHcalc$ABV - FPHcalc$ABV.control_NH
FPHcalc$delta.ABW <- FPHcalc$ABW - FPHcalc$ABW.control_NH
FPHcalc$delta.plato <- FPHcalc$Ea - FPHcalc$Ea.control_NH
FPHcalc$delta.SG <- FPHcalc$SG - FPHcalc$SG.control_NH
## the control samples have served their purpose, now remove them from dataset. The following calculations are only meaningful for dry-hopped samples.  
FPHcalc<- FPHcalc %>% filter(hop=="DH")
## *calcalate* corresponding CO2 production 
FPHcalc$calcCO2_increase <- FPHcalc$delta.ABW*(0.44/0.46)
##convert calcCO2_increase (in g/100mL) to calculated CO2 volumes added
## g/L = 10* g/100mL
## The conversion factor from volumes of CO2 to CO2 by weight (g/L) is 1.96. For example: 2.5 volumes x 1.96 = 4.9 g/l.
FPHcalc$calcCO2vols_increase <- FPHcalc$calcCO2_increase*10/1.96
## define "FPH" as amount produced per % dry-hops added (in g/100mL):
##  FPH = Fold Production due to Hops (fold-increase by mass: amount of given endpoint relative to amount of hops added)
##  
FPHcalc$FPH_EtOH = FPHcalc$delta.ABW/FPHcalc$hops_g_100mL
FPHcalc$FPH_CO2 = FPHcalc$calcCO2_increase/FPHcalc$hops_g_100mL
FPHcalc$FPH_plato = FPHcalc$delta.plato/FPHcalc$hops_g_100mL
## replacing time-zero time values with a very small number (rather than exactly zero) will prevent issues with analysis of nonlinear models
FPHcalc[FPHcalc$daysonhops==0,]$daysonhops <- 0.01
## and save the transformed data to csv:
write.csv(FPHcalc,"FPHcalc.csv", row.names = FALSE)
FPHcalc <- read.csv("FPHcalc.csv", stringsAsFactors = TRUE)
df<- FPHcalc %>% group_by(EXPTnew) %>%
  summarise_at(vars(hops_g_100mL,pounds_bbl,delta.ABV, delta.ABW, delta.plato, FPH_plato, FPH_EtOH, FPH_CO2, calcCO2vols_increase),funs(round(mean(.), 2))) %>%
  arrange(desc(FPH_EtOH)) 
df
sessionInfo()
R version 3.5.2 (2018-12-20)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] bindrcpp_0.2.2    plotly_4.8.0      drc_3.0-1         memisc_0.99.14.12
 [5] MASS_7.3-51.1     psych_1.8.4       caret_6.0-81      lattice_0.20-38  
 [9] forcats_0.3.0     stringr_1.3.1     purrr_0.2.4       readr_1.1.1      
[13] tidyr_0.8.0       tibble_1.4.2      tidyverse_1.2.1   gridExtra_2.3    
[17] ggplot2_3.0.0     dplyr_0.7.4      

loaded via a namespace (and not attached):
 [1] nlme_3.1-137       lubridate_1.7.4    httr_1.3.1         rprojroot_1.3-2   
 [5] repr_0.19.1        tools_3.5.2        backports_1.1.2    R6_2.2.2          
 [9] rpart_4.1-13       lazyeval_0.2.1     colorspace_1.3-2   nnet_7.3-12       
[13] withr_2.1.2        tidyselect_0.2.4   mnormt_1.5-5       curl_3.2          
[17] compiler_3.5.2     cli_1.0.0          rvest_0.3.2        xml2_1.2.0        
[21] sandwich_2.5-0     labeling_0.3       scales_0.5.0       mvtnorm_1.0-10    
[25] digest_0.6.15      foreign_0.8-71     rmarkdown_1.9      rio_0.5.16        
[29] base64enc_0.1-3    pkgconfig_2.0.1    htmltools_0.3.6    plotrix_3.7-4     
[33] htmlwidgets_1.3    rlang_0.3.1        readxl_1.1.0       rstudioapi_0.7    
[37] shiny_1.1.0        bindr_0.1.1        generics_0.0.2     zoo_1.8-4         
[41] jsonlite_1.5       crosstalk_1.0.0    gtools_3.8.1       ModelMetrics_1.2.2
[45] zip_1.0.0          car_3.0-2          magrittr_1.5       Matrix_1.2-15     
[49] Rcpp_0.12.16       munsell_0.4.3      abind_1.4-5        stringi_1.1.7     
[53] multcomp_1.4-10    yaml_2.1.19        carData_3.0-2      plyr_1.8.4        
[57] recipes_0.1.4      grid_3.5.2         promises_1.0.1     parallel_3.5.2    
[61] crayon_1.3.4       haven_1.1.1        splines_3.5.2      hms_0.4.2         
[65] knitr_1.21         pillar_1.2.2       reshape2_1.4.3     codetools_0.2-15  
[69] stats4_3.5.2       glue_1.2.0         evaluate_0.10.1    data.table_1.12.0 
[73] modelr_0.1.2       httpuv_1.4.3       foreach_1.4.4      cellranger_1.1.0  
[77] gtable_0.2.0       assertthat_0.2.0   xfun_0.4           gower_0.1.2       
[81] openxlsx_4.1.0     mime_0.5           prodlim_2018.04.18 xtable_1.8-2      
[85] broom_0.4.4        later_0.7.2        viridisLite_0.3.0  class_7.3-14      
[89] survival_2.43-3    timeDate_3043.102  iterators_1.0.10   lava_1.6.4        
[93] TH.data_1.0-10     ipred_0.9-8       
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2s6IE1vZGVsaW5nIHRoZSBGcmVzaGVuaW5nIFBvd2VyIG9mIHRoZSBIb3AiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo+ICpTdGVwMTogIE9wZW4gdGhpcyAuUk1EIGZpbGUgaW4gUiBTdHVkaW8gYW5kIGNsaWNrIHRoZSAiUHJldmlldyIgYnV0dG9uIGFib3ZlLiAgQSBmb3JtYXR0ZWQgdmVyc2lvbiBzaG91bGQgcG9wIHVwIGluIGEgYnJvd3NlciB3aW5kb3cuICBJRiBub3QsIHRyb3VibGVzaG9vdCB0aGF0ISoNCg0KDQojIG1hbnVhbCBvdXRsaW5lDQoNCjEuIEhvdXNla2VlcGluZw0KICAgIDEuICpTRVQgV09SS0lORyBESVJFQ1RPUlkqIA0KICAgIDEuIHRoaXMgKipSIE1hcmtkb3duICguUk1EKSoqIGRvY3VtZW50IHdhcyBjcmVhdGVkIHVzaW5nIFIgU3R1ZGlvDQogICAgICAgIDEuIHBsYWluIHRleHQNCiAgICAgICAgMS4gIlJlcHJvZHVjaWJsZSBSZXNlYXJjaCIgTW92ZW1lbnQNCiAgICAxLiAqKipSKioqIGZyb20gPGh0dHBzOi8vd3d3LnItcHJvamVjdC5vcmcvPg0KICAgIDEuICoqKlIgU3R1ZGlvKioqIGZyb20gPGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvPg0KICAgIDEuIEVudmlyb25tZW50IHRhYiAoUnN0dWRpbykNCiAgICAxLiBGaWxlcy9QbG90cy9QYWNrYWdlcy9IZWxwL1ZpZXdlciAgdGFiIChSc3R1ZGlvKQ0KICAgIDEuIFByZXZpZXcvdmlldyBhcyBIVE1MICAoUiBNYXJrZG93biBpbiBSc3R1ZGlvKQ0KICAgIDEuIE91dGxpbmUgdGFiIChSIE1hcmtkb3duIGluIFJzdHVkaW8pDQogICAgDQoqIEludHJvDQogICAgKyBTdGF0ZW1lbnQgb2YgUHJvYmxlbQ0KICAgICsgT3ZlcnZpZXcgb2YgRXhwZXJpbWVudGFsIERhdGENCiAgICArIE1lYXN1cmluZyB0aGUgRnJlc2hlbmluZyBQb3dlciBvZiB0aGUgSG9wIChyZWxhdGl2ZSB0byBub24tZHJ5aG9wcGVkIGNvbnRyb2xzKQ0KICAgICsgKmNhbGN1bGF0ZWQgbWV0cmljcyoNCiAgICAgICAgLSAkXERlbHRhJEFCViANCiAgICAgICAgLSAkXERlbHRhJEFCVw0KICAgICAgICAtICRcRGVsdGF7Y2FsY3VsYXRlZH0kQ08kXzIkDQogICAgICAgIC0gJFxEZWx0YSQgJmRlZztwbGF0bw0KICAgICAgICAtICRcRGVsdGEkU0ckXnsyMC8yMH0kDQogICAgICAgIC0gZm9sZC1pbmNyZWFzZSBldGhhbm9sIGZyb20gaG9wIGFkZGl0aW9uIChpbiAkXGZyYWN7cG91bmRzfXtiYmx9JCBhbmQgd3QlIG9yICRcZnJhY3tncmFtc317MTAwIG1MfSQpDQogICAgICAgIC0gZm9sZC1pbmNyZWFzZSBDTyRfMiQgZnJvbSBob3AgYWRkaXRpb24gKGluIGcvTCBhbmQgQ08kXzIkIHZvbHVtZXMpDQoqIExvYWQgTGlicmFyaWVzDQoqIERhdGEgSW1wb3J0LCBUaWR5IGFuZCBUcmFuc2Zvcm0NCiogRGF0YSBWaXN1YWxpemF0aW9uDQogICAgKyBzY2F0dGVyIHBsb3RzDQogICAgKyBzY2F0dGVyIHBsb3RzIHdpdGggYWRkZWQgZGltZW5zaW9ucw0KKiBNb2RlbGluZyBvdmVydmlldw0KICAgICsgbGluZWFyIHJlZ3Jlc3Npb24NCiAgICArIHJlc2lkdWFscw0KKiBNb2RlbGluZyBGUEgNCg0KDQojIGludHJvDQoNCiogWyoiRGF0YSBpcyBub3QgaW5mb3JtYXRpb24uIEluZm9ybWF0aW9uIGlzIG5vdCBrbm93bGVkZ2UuIEFuZCBrbm93bGVkZ2UNCmlzIGNlcnRhaW5seSBub3Qgd2lzZG9tLiIqXShodHRwOi8vd3d3LmRhdGFnb3Zlcm5hbmNlLmNvbS9xdW90ZXMva25vd2xlZGdlLXF1b3Rlcy8gIkNsaWZmb3JkIFN0b2xsIikNCiogW1dpY2toYW0ncyBmb3VyIHBpbGxhcnMgb2YgZGF0YSBhbmFseXNpczpdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovICJHcm9sZW11bmQgYW5kIFdpY2toYW0ncyBSIGZvciBEYXRhIFNjaWVuY2UiKQ0KICAgICogdGlkeQ0KICAgICogdHJhbnNmb3JtDQogICAgKiB2aXN1YWxpemUNCiAgICAqIG1vZGVsDQoNCmFzY2lpIGZvbnQvYXJ0IGZyb20gPGh0dHA6Ly93d3cubmV0d29yay1zY2llbmNlLmRlL2FzY2lpLz4NCmBgYHtyIGVjaG89VFJVRX0NCiMgICAgICAgICAgICAgICAgICAgICAgIF9fICAuX18gICAgLl9fXyAgICAgIA0KIyAgICAgICAgICAgICAgICAgICAgIF8vICB8X3xfX3wgX198IF8vX18uX18uDQojICAgICAgICAgICAgICAgICAgICAgXCAgIF9fXCAgfC8gX18gPCAgIHwgIHwNCiMgICAgICAgICAgICAgICAgICAgICAgfCAgfCB8ICAvIC9fLyB8XF9fXyAgfA0KIyAgICAgICAgICAgICAgICAgICAgICB8X198IHxfX1xfX19fIHwvIF9fX198DQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcL1wvICAgICANCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcLw0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHx8DQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfHwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8fA0KIyAgICAgICAgICAgICAgXyAgICAgICAgICAgICAgICAgICAgIC9cICAgICAgICAgICAgICAgDQojICAgICAgICAgICAgIHwgfF8gXyBfIF9fIF8gXyBfICBfX18vIF98X19fIF8gXyBfIF9fICANCiMgICAgICAgICAgICAgfCAgX3wgJ18vIF9gIHwgJyBcKF8tPCAgXy8gXyBcICdffCAnICBcIA0KIyAgICAgICAgICAgICAgXF9ffF98IFxfXyxffF98fF8vX18vX3wgXF9fXy9ffCB8X3xffF98DQojICAgICAgICAgICAgICAgICAgICAgICAgLy8gICAgICAgICAgICAgICAgICAgICAgXFwNCiMgICAgICAgICAgICAgICAgICAgICAgIC8vICAgICAgICAgICAgICAgICAgICAgICAgXFwNCiMgICAgICAgICAgICAgICAgICAgICAgLy8gICAgICAgICAgICAgICAgICAgICAgICAgIFxcDQojICAgICAgICAgICAgICAgICAgICAgLy8gICAgICAgICAgICAgICAgICAgICAgICAgICAgXFwNCiMgICAgICAgICAgXyAgICAgICAgIC8vICAgXyBfICAgICAgICAgICAgICAgICAgICAgICAgXFwgICBfICAgICBfIA0KIyAgICAgX18gXyhfKV9fX18gIF8gX18gX3wgKF8pX19fX19fICAgICAgICBfIF9fICBfX18gIF9ffCB8X19ffCB8DQojICAgICBcIFYgLyAoXy08IHx8IC8gX2AgfCB8IHxfIC8gLV8pPT09PT09fCAnICBcLyBfIFwvIF9gIC8gLV8pIHwNCiMgICAgICBcXy98Xy9fXy9cXyxfXF9fLF98X3xfL19fXF9fX3wgICAgICB8X3xffF9cX19fL1xfXyxfXF9fX3xffA0KYGBgDQoNClRoaXMgaXMgcHJpbWFyaWx5IGFuIGV4Y2VyY2lzZSBpbiBkYXRhIHdyYW5nbGluZyAodGlkeSt0cmFuc2Zvcm0pIGFuZCBtYXJrZG93biBmb3JtYXR0aW5nIHVzaW5nIFIgU3R1ZGlvLiAgSW4gdGhhdCBjb250ZXh0LCB0aGlzIGFuYWx5c2lzIGlzIGEgKnByZWxpbWluYXJ5KiBleHBsb3JhdGlvbiBvZiBwb3RlbnRpYWwgbWV0cmljcyBmb3IgKkhvcCBGcmVzaGVuaW5nIFBvd2VyKiAoc3lub255bXM6ICdkcnlob3AgY3JlZXAnLCAnQUJWIGNyZWVwJywgJ2RyeS1ob3AgY3JlZXAnKSBhbmQgdG8gbW9kZWwgdGhpcyBhc3BlY3Qgb2YgdGhlIHdhcm0tZHJ5aG9wcGluZyBwcm9jZXNzLiANCg0KIyMgU3RhdGVtZW50IG9mIFByb2JsZW0NClRoZSB3YXJtLWRyeWhvcHBpbmcgcHJvY2VzcyBjYW4gcHJvZm91bmRseSBpbXBhY3QgdGhlIGJvZHksIGFsY29ob2wgYW5kIHZpY2luYWwgZGlrZXRvbmUgY29udGVudCBvZiB0aGUgYmVlci4gIEEgY29tbW9uIG1ldHJpYyBmb3IgdGhpcyBwaGVub21lbm9uIGhhcyBub3QgYmVlbiBlc3RhYmxpc2hlZC4NCg0KIyMgT3ZlcnZpZXcgb2YgRXhwZXJpbWVudGFsIERhdGENClRocm91Z2ggZXhwZXJpZW5jZSBpbiBicmV3aW5nIHdlIGtub3cgdGhhdCAqKmVuZHBvaW50cyoqIG9mIGRyeWhvcHBpbmcgaW5jbHVkZToNCg0KKiBmbGF2b3IgaW1wYWN0IChhbHdheXMpDQoqIGltcGFjdCBvbiB2aXN1YWwvcHJlc2VudGF0aW9uIChzb21ldGltZXMpDQoqICpldGhhbm9sIGluY3JlYXNlKiBhY2NvbXBhbmllZCBieSBkZWNyZWFzZSBpbiBzcGVjaWZpYyBncmF2aXR5IChzb21ldGltZXMpDQoqIENPMiBpbmNyZWFzZSAoc29tZXRpbWVzKQ0KKiBkaWFjZXR5bC9WREsgaW5jcmVhc2UgKHNvbWV0aW1lcykNCiogYW5kIHNvIG9uLi4uDQoNCkV4cGVyaW1lbnRzIHdlcmUgY2FycmllZCBvdXQgd2hlcmUgDQoNCjEuIG11bHRpcGxlIHJlcGxpY2F0ZSAyNTAtbUwgc2FtcGxlcyBvZiBbbmVhcmx5XSBlbmQtZmVybWVudGVkIGJlZXIgd2VyZSBjb2xsZWN0ZWQgZnJvbSBmZXJtZW50ZXJzIGludG8gMTJveiBhbWJlciBib3R0bGVzDQoxLiB0aGUgc2FtcGxlcyB3ZXJlIHJhbmRvbWl6ZWQgaW50byBncm91cHMgb2YgdGhyZWUsIHRoZW4gZWFjaCBncm91cCB3YXMgZWl0aGVyDQogICAgKiBkcnktaG9wcGVkIGF0IH4xLjAgcG91bmRzL2JibCAodHJlYXRtZW50KSwgb3INCiAgICAqIG5vdCBkcnktaG9wcGVkIChjb250cm9sKQ0KMS4gYWxsIHdlcmUgaGFuZC1jcmltcGVkIHdpdGggZm9pbCwgc3RvcmVkIGZvciB2YXJpb3VzIHRpbWVzIChtb3N0bHkgYXQgcm9vbSB0ZW1wLCBhIGZldyBpbiB0aGUgY29sZCksIHRoZW4gdGVzdGVkIG9uIGFuIEFudG9uIFBhYXIgRE1BNDUwMC9BbGNvbHl6ZXIgY2xhc3NpYw0KICAgICAgICANCkZvY3VzaW5nIG9uICpldGhhbm9sIGluY3JlYXNlIGFzIHRoZSBlbmRwb2ludCosIHdlIGtub3cgdGhlc2UgdG8gYmUgKipyZWxldmFudCBmYWN0b3JzKio6DQoNCiogdmFyaWV0eSBvZiBob3BzIChmaXZlIGRpZmZlcmVudCB2YXJpZXRpZXMpDQoqIHByZXNlbmNlIG9mIGxpdmUgeWVhc3QgKHRydWUgaW4gYWxsIGNhc2VzIGZvciB0aGVzZSBkYXRhKQ0KKiB0ZW1wLkMgPSB0ZW1wZXJhdHVyZSBkdXJpbmcgZHJ5aG9wcGluZyAobW9zdGx5IHdhcm0sIHdpdGggYSBmZXcgaW4gdGhlIGNvbGQpDQoqIGFtb3VudCBvZiBjb250YWN0IHRpbWUgYmV0d2VlbiBob3BzIGFuZCBiZWVyICh1cCB0byA3IHdlZWtzKQ0KDQoqRm9yIG1vcmUgZGV0YWlscyBvbiB0aGlzIGRhdGFzZXQgc2VlIHRoZSBbbWFudXNjcmlwdF0oaHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL2RvaS9mdWxsLzEwLjEwODAvMDM2MTA0NzAuMjAxOC4xNDY5MDgxP3Njcm9sbD10b3AmbmVlZEFjY2Vzcz10cnVlICJLaXJrZW5kYWxsMjAxOCIpLiAgDQoqIHBsZWFzZSBzZW5kIGNvbXBsYWludHMgYW5kIGNvcnJlY3Rpb25zIHRvIGx1a2UuY2hhZHdpY2tAZ21haWwuY29tIQ0KKiBEYXRhIGFyZSBmcm9tIHRoZSBmaWxlICJ1amJjX2FfMTQ2OTA4MV9zbTU0OTYudHh0IiAoc3VwcGxlbWVudGFyeSBkYXRhIGZyb20gSmFjb2IgQS4gS2lya2VuZGFsbCwgQ2FydGVyIEEuIE1pdGNoZWxsICYgTHVjYXMgUi4gQ2hhZHdpY2sgKDIwMTgpOiBUaGUgRnJlc2hlbmluZyBQb3dlciBvZiBDZW50ZW5uaWFsIEhvcHMsICpKb3VybmFsIG9mIHRoZSBBbWVyaWNhbiBTb2NpZXR5IG9mIEJyZXdpbmcgQ2hlbWlzdHMqIFZvbHVtZSA3NiwgSXNzdWUgMywgUGFnZXMgMTc4LTE4NCAoKioyMDE4KiopIERPSTogMTAuMTA4MC8wMzYxMDQ3MC4yMDE4LjE0NjkwODEqIGF2YWlsYWJsZSBmcm9tIDxodHRwczovL3d3dy50YW5kZm9ubGluZS5jb20vZG9pL2Z1bGwvMTAuMTA4MC8wMzYxMDQ3MC4yMDE4LjE0NjkwODE/c2Nyb2xsPXRvcCZuZWVkQWNjZXNzPXRydWU+DQoNCg0KIyBsb2FkIGxpYnJhcmllcw0KYGBge3IgcGFja2FnZS5sb2FkLCByZXN1bHRzPSdhc2lzJywgZWNobz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0gDQojLnJzLnJlc3RhcnRSKCkgICMgcmVzdGFydCBSIA0Kc2Vzc2lvbkluZm8oKQ0KDQojZGF0YSB3cmFuZ2xpbmcNCiNpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpICAjIGRwbHlyICJkYXRhIHBseWVyIiAgDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShncmlkRXh0cmEpICAjIGZvciBncmlkLmFycmFuZ2UgaW4gUiBtYXJrZG93bg0KDQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQoNCmxpYnJhcnkoY2FyZXQpICMgZm9yIGR1bW15VmFycyBmdW5jdGlvbg0KbGlicmFyeShwc3ljaCkgIyBmb3IgcGFpcnMucGFuZWxzIGZ1bmN0aW9uDQpsaWJyYXJ5KG1lbWlzYykgICMgY29tcGFyZSBtb2RlbHMNCg0KbGlicmFyeShkcmMpICAjICJkcmMiID0gZG9zZSByZXNwb25zZSBjdXJ2ZXMgKGNvbnRhaW5zIGEgc3VpdGUgb2YgZmxleGlibGUgYW5kIHZlcnNhdGlsZSBtb2RlbCBmaXR0aW5nIGFuZCBhZnRlci1maXR0aW5nIGZ1bmN0aW9ucykNCmxpYnJhcnkocGxvdGx5KQ0KDQpgYGANCg0KDQoNCj4gZmxhc2ggZm9yd2FyZCEgc2Nyb2xsIHRvIHRoZSBib3R0b20gYW5kIHJ1biBiaWdjaHVua190aWR5dHJhbnNmb3JtDQoNCg0KI2ltcG9ydCBkYXRhDQpgYGB7cn0NCnJtKGxpc3QgPSBscygpKSAjIGNsZWFyIHdvcmtzcGFjZQ0KbXlkYXRhIDwtcmVhZC5jc3YoInVqYmNfYV8xNDY5MDgxX3NtNTQ5Ni50eHQiLHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkgICMjIFNQRUNJRlkgZmlsZW5hbWUNCg0KbXlkYXRhICU+JSBmaWx0ZXIoZXhwdCA9PSIgMkEiKSAlPiUgZHBseXI6OnNlbGVjdChzYW1wbGVfaWQpDQoNCiNkcHV0KG5hbWVzKG15ZGF0YSkpDQoNCmBgYA0KDQoNCg0KIyBtYW51YWwgYWRkIHRpbWUgemVybyB0byAiZGF5cyBvbiBob3BzIiBleHBlcmltZW50IQ0KQW50b24gUGFhciBkYXRhIGF0IHRpbWUgemVybyBmb3Iga2luZXRpY3MgZXhwZXJpbWVudCAodHJpcGxpY2F0ZSBzYW1wbGVzIHJ1biBpbW1lZGlhdGVseSBhZnRlciBzYW1wbGUgY29sbGVjdGlvbiwgd2hpbGUgdGhlIHJlbWFpbmRlciB3ZXJlIGJlaW5nIGRyeS1ob3BwZWQpOg0KRGF0ZQl0aW1lCXNhbXBsZSBuby4JQUJWCUFCVwlPRSAoUCkJRXIJRWEJU0cgMjAvMjAJUkRGCUFERiBzYW1wbGVfaWQNCjA3LzI3LzE3CTM6MjYgUE0JMDNfCTYuNjgJNS4yCTE1Ljk1CTYuMDgJMy43NAkxLjAxNDYyCTYzLjg4CTc2LjU4CUpLMS0yMC00MQ0KMDcvMjcvMTcJMzozMCBQTQkwNF8JNi42OAk1LjIJMTUuOTUJNi4wOAkzLjczCTEuMDE0NjEJNjMuODkJNzYuNglKSzEtMjAtNTANCjA3LzI3LzE3CTM6MzQgUE0JMDVfCTYuNjgJNS4yCTE1Ljk1CTYuMDgJMy43NAkxLjAxNDYyCTYzLjg4CTc2LjU4CUpLMS0yMC04Nw0KDQpgYGB7cn0NCiMjIGZpbGwgaW4gTUlTU0lORyBEQVRBIDooICBhZGQgdGltZSB6ZXJvIGRhdGEgZm9yIHRpbWUgc2VyaWVzIGFuYWx5c2lzKQ0KDQojIyBuZXN0ZWQgaWZlbHNlIHRvIGFkZCB0aW1lIHplcm8gcGxhdG8gdmFsdWVzIGZvciBlYWNoIGV4cHQ6DQpteWRhdGE8LSBteWRhdGEgJT4lIG11dGF0ZSguLCBpbml0aWFsX3BsYXRvID0gaWZlbHNlKGV4cHQ9PSIgMkEiLCAzLjc0LCBpZmVsc2UoZXhwdD09IiAxQSIsIDMuNjksIGlmZWxzZShleHB0PT0iIDFCIiwgMy41MywgaWZlbHNlKGV4cHQ9PSIgM0IiLCAzLjU0LCBpZmVsc2UoZXhwdD09IiA0QiIsIDQuNjYsIE5BKSkpKSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiMjIGFkZCBjb21wbGV0ZSB0aW1lLXplcm8gYW50b24gcGFhciBkYXRhIGZvciB0aW1lIHNlcmllcyAoZXhwdDJBKQ0KIyMgcXVhZHJ1cGxlLXVwIGVhY2ggdHJpcGxpY2F0ZSB0aW1lLXplcm8gbWVhc3VyZW1lbnQgKG9uZSBjb3B5IGZvciBlYWNoIE5IL0RIICYgcm91c2Uvc3RpbGwgY29tYm8pDQojIyBub3RlOiAgdGhpcyBwcmFjdGljZSBpcyBvZiBxdWVzdGlvbmFibGUgc3RhdGlzdGljYWwgcmlnb3IuICBJbiByZXRyb3NwZWN0IGl0IHdvdWxkIGhhdmUgYmVlbiBiZXR0ZXIgKGFuZCBhIGxvdCBlYXNpZXIpIHRvIGp1c3QgcnVuIDEyIHNhbXBsZXMgdGhyb3VnaCBBbnRvbiBvbiBkYXkwIQ0KdGltZXplcm8gPC0gbXlkYXRhICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBhcnJhbmdlKHNwZWNpYWxfZ3JvdXApICU+JSBzbGljZSgxOjEyKQ0KI2RhdGEuZW50cnkodGltZXplcm8pDQp0aW1lemVybyRUZXN0X0RhdGUgPC0gcmVwKGMoIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIpLDQpDQp0aW1lemVybyRUZXN0LnRpbWUgPC0gcmVwKGMoIjM6MjYgUE0iLCAiMzozMCBQTSIsICIzOjM0IFBNIiksNCkNCnRpbWV6ZXJvJEFCViA8LSA2LjY4DQp0aW1lemVybyRBQlcgPC0gNS4yDQp0aW1lemVybyRPRSA8LSAxNS45NQ0KdGltZXplcm8kRXIgPC0gNi4wOA0KdGltZXplcm8kRWEgPC0gcmVwKGMoMy43NCwgMy43MywgMy43NCksNCkNCnRpbWV6ZXJvJFNHIDwtIHJlcChjKDEuMDE0NjIsIDEuMDE0NjEsIDEuMDE0NjIpLDQpDQp0aW1lemVybyRSREYgPC0gcmVwKGMoNjMuODgsIDYzLjg5LCA2My44OCksNCkNCnRpbWV6ZXJvJEFERiA8LSByZXAoYyg3Ni41OCwgNzYuNjAsIDc2LjU4KSw0KQ0KdGltZXplcm8kQ2Fsb3JpZXMgPC0gcmVwKGMoMjE1LjM3LDIxNS4zNCwyMTUuMzUpLDQpDQp0aW1lemVyb1t0aW1lemVybyRob3A9PSJESCIsXSRjb250YWN0X2RheXMgPC0wDQp0aW1lemVyb1ssYygyODozMSldIDwtIDANCm15ZGF0YSA8LSByYmluZCh0aW1lemVybyxteWRhdGEpDQp3cml0ZS5jc3YobXlkYXRhLCAicXVpY2tpZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCm15ZGF0YTwtcmVhZC5jc3YoInF1aWNraWUuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQojIyMgIHRoaXMgaXMgYSByb3V0aW5lIGZvciBpbnNwZWN0aW5nIGRhdGEgIyMjDQpteWRhdGF0eXBlczwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2FwcGx5KG15ZGF0YSwgY2xhc3MpKSkgICMjIHRhYmxlIG9mIGRhdGF0eXBlcw0KbXlkYXRhdHlwZXMkaGVhZGVycyA8LSByb3duYW1lcyhteWRhdGF0eXBlcykgICAgIyMgY29udmVydCByb3duYW1lcyB0byB2YWx1ZXMNCm5hX2NvdW50IDwtYXMuZGF0YS5mcmFtZShzYXBwbHkobXlkYXRhLCBmdW5jdGlvbih5KSBzdW0obGVuZ3RoKHdoaWNoKGlzLm5hKHkpKSkpKSkgIyMgY291bnQgTkFzIGJ5IGNvbHVtbg0KbXlkYXRhT3ZlcnZpZXc8LWFzLmRhdGEuZnJhbWUoY2JpbmQobmFfY291bnQsbXlkYXRhdHlwZXMpKSAgIyMgdGFibGUgb2YgTkEgY291bnRzIGFuZCBkYXRhdHlwZXMNCm5hbWVzKG15ZGF0YU92ZXJ2aWV3KSA8LSBjKCJOQV9jb3VudCIsIkRhdGFfQ2xhc3MiLCAiSGVhZGVyIikNCnN1bShpcy5uYShteWRhdGEpKSAgIyMgdG90YWwgY291bnQgb2YgTkEgdmFsdWVzIGluIGVudGlyZSBzaGVldCAoMjMxIGluIHRoaXMgY2FzZSkNCm15ZGF0YU92ZXJ2aWV3ICU+JSBmaWx0ZXIoTkFfY291bnQ+MCkgICAgIyMgYnJlYWtkb3duIG9mIE5BIHZhbHVlcw0KYGBgDQoNCg0KIyB0aWR5ICYgdHJhbnNmb3JtIA0KDQo+IG5vdGU6IHRoaXMgaXMgYSBiYXNlIFIgK2RwbHlyIGRhdGEgd3JhbmdsaW5nIGV4ZXJjaXNlISBpbiBnZW5lcmFsIGl0J3MgYmVzdCB0byB1c2UgbHVicmlkYXRlIHBhY2thZ2UgZm9yIGRhdGUvdGltZXN0YW1wcyEgDQoNCiogY3JlYXRlIGRhdGUgY29sdW1uIChvbmUgYXQgYSB0aW1lKQ0KICAgICogaGVyZSB3ZSdyZSBjcmVhdGluZyBuZXcgY29sdW1ucyBpbiBQT1NJWGN0IGZvcm1hdCwgYmFzZWQgb24gdGhlIHBhcnRpY3VsYXIgZGF0ZSBmb3JtYXQgdXNlZCBpbiB0aGUgc291cmNlIGRhdGEgKHVqYmNfYV8xNDY5MDgxX3NtNTQ5Ni50eHQ7IHNlZSA/YXMuUE9TSVhjdCk6DQpgYGB7cn0NCnRlc3RfZGY8LSBteWRhdGEgIyAob3JpZ2luYWwgZGF0YWZyYW1lIHRvIGJlIHVzZWQgZm9yIHRlc3RpbmcvaWxsdXN0cmF0aW9uKQ0KDQp4X2JyZXdfZGF0ZS5QT1NJWGN0PC0gYXMuUE9TSVhjdChteWRhdGEkYnJld19kYXRlLCBmb3JtYXQgPSAiJW0vJWQvJVkiKSAjIGFzIGEgc3RhbmRhbG9uZSB2ZWN0b3IgKFVTRUxFU1MgaGVyZSksIG9yOg0KdGVzdF9kZiRicmV3X2RhdGUuUE9TSVhjdCA8LWFzLlBPU0lYY3QobXlkYXRhJGJyZXdfZGF0ZSwgZm9ybWF0ID0gIiVtLyVkLyVZIikgICMgYXMgYSAibmV3IGNvbHVtbiIgaW4gb3VyIGRhdGFmcmFtZQ0KYGBgDQoNCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogDQogICAgKiBjcmVhdGUgbXVsdGlwbGUgZGF0ZSBjb2x1bW5zIHVzaW5nIEZPUkxPT1ANCihoZXJlIHdlIHRha2UgYWR2YW50YWdlIG9mIHRoZSBwYXR0ZXJuIHRoYXQgb2Ygb3VyIChjaGFyYWN0ZXIpIGRhdGUvdGltZSBjb2x1bW5zIGhhdmUgdGhlIHN0cmluZyAiZGF0ZSIgaW4gdGhlIGhlYWRlcm5hbWUuICBGaXJzdCBtYWtlIGNoYXJhY3RlciB2ZWN0b3Igb2YgYWxsIGNvbHVtbiBuYW1lcyBjb250YWluaW5nIHN0cmluZyAiZGF0ZSIsIHRoZW4gcnVuIEZPUkxPT1Agb3ZlciBlYWNoIGRhdGUgY29sdW1uKS4gIFRoaXMgZm9ybG9vcCB3aWxsIGNvbnZlcnQgZGF0ZWNvbHMgaW50byBQT1NJWGN0IGZvcm1hdCAoUi91bml2ZXJzYWwgZGF0ZXRpbWUgZm9ybWF0KS4gIFRoZXkgc2F5IGl0J3MgICoqYmVzdCB0byBhdm9pZCBmb3Jsb29wcyoqIGluIGdlbmVyYWwsIGFuZCAqKmluIHRoaXMgY2FzZSB5b3Ugd291bGQgcHJvYmFibHkgdXNlIGZ1bmN0aW9ucyBmcm9tIGx1YnJpZGF0ZSBwYWNrYWdlKiogLSBhbmQgaW4gZ2VuZXJhbCBiZSBhd2FyZSB0aGVyZSdzIHByb2JhYmx5IGEgc3BlY2lmaWMgdG9vbCBmb3IgdGhlIHRhc2sgYXQgaGFuZCkhIQ0KYGBge3J9DQpkYXRlY29sczwtIGRwdXQobmFtZXMoZHBseXI6OnNlbGVjdChteWRhdGEsIG1hdGNoZXMoImRhdGUiKSkpKSAgIyMgaGVhZGVycyBjb250YWluaW5nIHN0cmluZyAiZGF0ZSIgPT4gYygiYnJld19kYXRlIiwgInNhbXBsZV9jb2xsZWN0aW9uX2RhdGUiLCAiZHJ5aG9wX2RhdGUiLCAiVGVzdF9EYXRlIikNCiMjIEZPUkxPT1ANCmZvciAoaWNvbCBpbiBkYXRlY29scykgew0KICBuZXdjb2wgPSBwYXN0ZTAoaWNvbCwiLlBPU0lYY3QiKQ0KICMgcHJpbnQobmV3Y29sKQ0KICBteWRhdGFbLCBuZXdjb2xdID0gYXMuUE9TSVhjdChteWRhdGFbLCBpY29sXSxmb3JtYXQgPSAiJW0vJWQvJVkiKSAjIyMgIENSRUFURSBORVcgY29sdW1ucyB3aXRoIFBPU0lYY3QgICANCiMgIG15ZGF0YVssIG5ld2NvbF0gPSBhcy5QT1NJWGN0KGFzLm51bWVyaWMobXlkYXRhWywgaWNvbF0pICAqICg2MCo2MCoyNCksIG9yaWdpbj0iMTg5OS0xMi0zMCIpICMjIyAgbWljcm9zb2Z0IHRpbWVzDQp9DQpgYGANCg0KDQoqIGNyZWF0ZSBkYXlvZmFkZGl0aW9uLCBkYXlzb25ob3BzLCBob3BzX2dfMTAwbUwsIHBvdW5kc19iYmwgdmFyaWFibGVzIHdpdGggZHBseXIgIm11dGF0ZSINCmBgYHtyfQ0KbXlkYXRhPC0gbXlkYXRhICU+JSANCiAgbXV0YXRlKGRheW9mYWRkaXRpb24gPSBhcy5pbnRlZ2VyKGFzLm51bWVyaWMoZGlmZnRpbWUoZHJ5aG9wX2RhdGUuUE9TSVhjdCxicmV3X2RhdGUuUE9TSVhjdCkpKSwNCiAgICAgICAgIGRheXNvbmhvcHMgPSBhcy5pbnRlZ2VyKGFzLm51bWVyaWMoZGlmZnRpbWUoVGVzdF9EYXRlLlBPU0lYY3QsZHJ5aG9wX2RhdGUuUE9TSVhjdCkpLyg2MCo2MCoyNCkpLA0KICAgICAgICAgaG9wc19nXzEwMG1MID0gKG1nX2hvcHMvdm9sdW1lX21MKS8xMCwNCiAgICAgICAgIHBvdW5kc19iYmwgPSAobWdfaG9wcy92b2x1bWVfbUwpKjExNy80NTQNCiAgICAgICAgICkNCmBgYA0KDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IG1hbnVhbGx5IHVucGFjayBvdmVybG9hZGVkIGNvbHVtbnMgd2l0aCBncmVwbA0KYGBge3J9DQpteWRhdGEkT1g8LWdyZXBsKCJPWCIsIG15ZGF0YSRIb3BfdHlwZSkgICAgICAgIyMgIGNyZWF0ZSBsb2dpY2FsICJPWCIgY29sdW1uDQpteWRhdGEkcm91c2U8LWdyZXBsKCJyb3VzZSIsIG15ZGF0YSRzcGVjaWFsX2dyb3VwKSMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRHcmluZDwtZ3JlcGwoIkdyaW5kIiwgbXlkYXRhJEhvcF90eXBlKSAgICAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJENvbmU8LWdyZXBsKCJDb25lIiwgbXlkYXRhJEhvcF90eXBlKSAgICAgICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkaGFydmVzdDIwMTQ8LWdyZXBsKCIxNCIsIG15ZGF0YSRIb3BfdHlwZSkgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRoYXJ2ZXN0MjAxNTwtZ3JlcGwoIjE1IiwgbXlkYXRhJEhvcF90eXBlKSAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJGhhcnZlc3QyMDE3PC1ncmVwbCgiMTciLCBteWRhdGEkSG9wX3R5cGUpICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpgYGANCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogaWZlbHNlIHN0YXRlbWVudHMgZm9yIGhhcnZlc3R5ZWFyLCBmb3JtX29mX2hvcHMsIGFuZCBESF90ZW1wDQpgYGB7cn0NCiMjIGlmZWxzZSBzdGF0ZW1lbnQgZm9yIGhhcnZlc3R5ZWFyIChpZiBuZWl0aGVyIDIwMTQgbm9yIDIwMTUgbm9yIDIwMTcsIHRoZW4gMjAxNikNCm15ZGF0YSRoYXJ2ZXN0WWVhciA8LSBpZmVsc2UoDQogIG15ZGF0YSRoYXJ2ZXN0MjAxND09VFJVRSwgMjAxNCwNCiAgaWZlbHNlKG15ZGF0YSRoYXJ2ZXN0MjAxNT09VFJVRSwgMjAxNSwgDQogICAgICAgICBpZmVsc2UobXlkYXRhJGhhcnZlc3QyMDE3PT1UUlVFLCAyMDE3LCAyMDE2KSkpDQpkcHV0KGxldmVscyhhcy5mYWN0b3IobXlkYXRhJGhhcnZlc3RZZWFyKSkpDQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciBmb3JtIG9mIGhvcHMgKGlmIG5laXRoZXIgY29uZSBub3IgZ3JvdW5kIG5vciBOSCwgdGhlbiBwZWxsZXQpIA0KbXlkYXRhJGZvcm1fb2ZfaG9wcyA8LSBpZmVsc2UoDQogIG15ZGF0YSRDb25lPT1UUlVFLCAiY29uZSIsDQogIGlmZWxzZShteWRhdGEkR3JpbmQ9PVRSVUUsICJncm91bmQiLA0KICAgICAgICAgaWZlbHNlKG15ZGF0YSRIb3BfdHlwZT09Ik5IIiwgIk5IIiwgInBlbGxldCIpKSkNCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkZm9ybV9vZl9ob3BzKSkpDQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciB0ZW1wZXJhdHVyZSBncmVhdGVyIG9yIGxlc3MgdGhhbiAxMCANCm15ZGF0YSRESF90ZW1wIDwtIGlmZWxzZSgNCiAgbXlkYXRhJHRlbXAuQzwxMCwgImNvbGQiLA0KICBpZmVsc2UobXlkYXRhJHRlbXAuQz4xMCwgIndhcm0iLCAic29tZXRoaW5nIGVsc2UiKSkNCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkREhfdGVtcCkpKQ0KYGBgDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IGNyZWF0ZSAidmFyaWV0eSIgY29sdW1uIHN0YXJ0aW5nIHdpdGggb3ZlcmxvYWRlZCAiSG9wX3R5cGUiIGNvbHVtbiwgdGhlbiBzdHJpcHBpbmcgYXdheSBhbGwgdGhlIG5vbi12YXJpZXR5IGluZm9ybWF0aW9uDQpgYGB7cn0NCm15ZGF0YSR2YXJpZXR5PC1teWRhdGEkSG9wX3R5cGUNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiWzAtOV0rIiwiIiwgbXlkYXRhJHZhcmlldHkpICMjIHJlbW92ZSBhbGwgbnVtYmVycw0KbXlkYXRhJHZhcmlldHk8LSBnc3ViKCJPWCIsIiIsIG15ZGF0YSR2YXJpZXR5KSAgICAgIyMgcmVtb3ZlIHNwZWNpZmljIHRleHQNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiR3JpbmQiLCIiLCBteWRhdGEkdmFyaWV0eSkNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiQ29uZSIsIiIsIG15ZGF0YSR2YXJpZXR5KQ0KbXlkYXRhJHZhcmlldHk8LSBnc3ViKCIgIiwiIiwgbXlkYXRhJHZhcmlldHkpICAgICAgIyMgcmVtb3ZlIHNwYWNlcw0KZHB1dChsZXZlbHMoYXMuZmFjdG9yKG15ZGF0YSR2YXJpZXR5KSkpDQpgYGANCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogY3JlYXRlICJFWFBUbmV3IiAoZWFjaCB1bmlxdWUgZXhwZXJpbWVudGFsIGdyb3VwIGRlc2lnbmF0ZWQgaW4gYSBzaW5nbGUgdmFyaWFibGUpDQpgYGB7cn0NCm15ZGF0YTwtIG15ZGF0YSAlPiUgbXV0YXRlKEVYUFRuZXc9cGFzdGUwKCJncm91cCIsZXhwdCwgc3Vic3RyKHNwZWNpYWxfZ3JvdXAsIDEsMiksYXMuY2hhcmFjdGVyKHJvdXNlKSkpDQojIGNsZWFuIGl0IHVwIGJ5IHJlbW92aW5nICJOQSIgYW5kIGFueSBzcGFjZXMgZHVlIHRvIGNhbmFyeWNvZGUgYnVnDQpteWRhdGEkRVhQVG5ldyA8LSBnc3ViKCIgIiwiIiwgbXlkYXRhJEVYUFRuZXcpICAjIyByZW1vdmUgYW55IHNwYWNlcw0KbXlkYXRhJEVYUFRuZXcgPC0gZ3N1YigiTkEiLCIiLCBteWRhdGEkRVhQVG5ldykgIyMgcmVtb3ZlICJOQSINCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkZXhwdCkpKQ0KZHB1dChsZXZlbHMoYXMuZmFjdG9yKG15ZGF0YSRFWFBUbmV3KSkpDQpgYGANCg0KDQojIyBzZWxlY3QgYW5kIHJlYXJyYW5nZSBjb2x1bW5zIA0KKGV4cGVyaW1lbnRhbCBmYWN0b3JzIG9uIHRoZSBsZWZ0LCB0aGVuIG1lYXN1cmVtZW50cywgZm9sbG93ZWQgYnkgY2FsY3VsYXRpb25zIGFuZCB0aGVuIGFsbCBkYXRlIGNvbHVtbnMgb24gdGhlIHJpZ2h0KToNCmBgYHtyfQ0KbXlkYXRhIDwtIG15ZGF0YSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2FtcGxlX2lkLGV4cHQsIEVYUFRuZXcsIGhvcCwgQklOaG9wLCB2YXJpZXR5LCBPWCwgaGFydmVzdFllYXIsIGZvcm1fb2ZfaG9wcyxzcGVjaWFsX2NvbmRpdGlvbnMsIHJvdXNlLCBkYXlzb25ob3BzLCBkYXlvZmFkZGl0aW9uLCBESF90ZW1wLCB0ZW1wLkMsIGhvcHNfZ18xMDBtTCwgcG91bmRzX2JibCwNCiAgICAgICAgIEFCViwgQUJXLCBPRSwgRXIsIEVhLCBTRywgUkRGLCBBREYsIENhbG9yaWVzLCANCiAgICAgICAgIGRob3BfZGF5LCBjb250YWN0X2RheXMsIFJFRl9OSCwgQUJWX2luY3JlYXNlLCANCiAgICAgICAgIGJyZXdfZGF0ZS5QT1NJWGN0LCBzYW1wbGVfY29sbGVjdGlvbl9kYXRlLlBPU0lYY3QsIGRyeWhvcF9kYXRlLlBPU0lYY3QsVGVzdF9EYXRlLlBPU0lYY3QsIGluaXRpYWxfcGxhdG8pDQojIyByZW1vdmUgIi5QT1NJWGN0IiBzdWZmaXguICBMZWF2aW5nIGl0IGFzLWlzIHdpbGwgb25seSBhZGQgdG8gY29uZnVzaW9uIGlmL3doZW4gdGhlc2UgZGF0YSBhcmUgc2F2ZWQgYW5kIHJlLWltcG9ydGVkIChhbmQgYmVjb21lICdjaGFyYWN0ZXInIGZvcm1hdCEpDQpjb2xuYW1lcyhteWRhdGEpID0gZ3N1YigiLlBPU0lYY3QiLCAiIiwgY29sbmFtZXMobXlkYXRhKSkNCmBgYA0KDQoNCg0KIyNjb21wdXRlIG1lYW4gTkggKGNvbnRyb2wpIHZhbHVlcyANCihOSCA9IG5vdCBkcnktaG9wcGVkKSANCmBgYHtyfQ0KIyMgbWVhbiBOSCAoY29udHJvbCkgdmFsdWVzIGZvciBlYWNoIEVYUFRuZXcgZ3JvdXANCm1lYW4uY29udHJvbF9OSDwtIG15ZGF0YSAlPiUgDQogIGdyb3VwX2J5KEVYUFRuZXcpICU+JQ0KICBmaWx0ZXIoaG9wPT0iTkgiKSAlPiUNCiAgc3VtbWFyaXNlX2F0KHZhcnMoQUJWLCBBQlcsIEVhLCBTRyksZnVucyhtZWFuLCBuKCkpKQ0KIyMgcmVwbGFjZSAiX21lYW4iIHdpdGggIi5jb250cm9sX05IIiBpbiBjb2x1bW4gbmFtZXMNCmNvbG5hbWVzKG1lYW4uY29udHJvbF9OSCkgPSBnc3ViKCJfbWVhbiIsICIuY29udHJvbF9OSCIsIGNvbG5hbWVzKG1lYW4uY29udHJvbF9OSCkpDQpgYGANCg0KDQojI2NvbXB1dGUgJFxEZWx0YSQgdmFsdWVzIChjaGFuZ2VzIHJlbGF0aXZlIHRvIHVuLWRyeWhvcHBlZCBjb250cm9scykgdXNpbmcgb2JqZWN0cyBjcmVhdGVkIGFib3ZlLi4uDQoqIGRpZmZlcmVuY2Ugb2YgZWFjaCBBQlYsQUJXLEVhLCBhbmQgU0cgZGF0YSBwb2ludCBmcm9tIGNvcnJlc3BvbmRpbmcgbWVhbi5jb250cm9sX05IIHZhbHVlcyAodW5ob3BwZWQgc2FtcGxlcyBpbiBzYW1lIEVYUFRuZXcgZ3JvdXApDQpgYGB7cn0NCiMjIGZpcnN0IGpvaW4gb3VyIGRhdGEgd2l0aCBtZWFuIEFCViBmb3IgdW5ob3BwZWQgc2FtcGxlcyBpbiBnaXZlbiBleHBlcmltZW50IChtZWFuLmNvbnRyb2xfTkg7IGNhbGN1bGF0ZWQgYWJvdmUpDQoNCkZQSGNhbGM8LSBsZWZ0X2pvaW4obXlkYXRhLCBtZWFuLmNvbnRyb2xfTkgsIGJ5PSJFWFBUbmV3IikNCg0KIyMgY2FsY3VsYXRlIEFCV19pbmNyZWFzZSBieSBzdWJ0cmFjdGluZyBlYWNoIGluZGl2aWR1YWwgQUJXIG1lYXN1cmVtZW50IGZyb20gcmVzcGVjdGl2ZSBtZWFuLmNvbnRyb2xfTkg6DQpGUEhjYWxjJGRlbHRhLkFCViA8LSBGUEhjYWxjJEFCViAtIEZQSGNhbGMkQUJWLmNvbnRyb2xfTkgNCkZQSGNhbGMkZGVsdGEuQUJXIDwtIEZQSGNhbGMkQUJXIC0gRlBIY2FsYyRBQlcuY29udHJvbF9OSA0KRlBIY2FsYyRkZWx0YS5wbGF0byA8LSBGUEhjYWxjJEVhIC0gRlBIY2FsYyRFYS5jb250cm9sX05IDQpGUEhjYWxjJGRlbHRhLlNHIDwtIEZQSGNhbGMkU0cgLSBGUEhjYWxjJFNHLmNvbnRyb2xfTkgNCg0KIyMgdGhlIGNvbnRyb2wgc2FtcGxlcyBoYXZlIHNlcnZlZCB0aGVpciBwdXJwb3NlLCBub3cgcmVtb3ZlIHRoZW0gZnJvbSBkYXRhc2V0LiBUaGUgZm9sbG93aW5nIGNhbGN1bGF0aW9ucyBhcmUgb25seSBtZWFuaW5nZnVsIGZvciBkcnktaG9wcGVkIHNhbXBsZXMuICANCkZQSGNhbGM8LSBGUEhjYWxjICU+JSBmaWx0ZXIoaG9wPT0iREgiKQ0KYGBgDQoNCg0KIyMgKmNhbGN1bGF0ZSogY29ycmVzcG9uZGluZyBDTzIgcHJvZHVjdGlvbiANCiogZm9sbG93aW5nIEJhbWZvcnRoIChkZXNjcmliaW5nIEJhbGxpbmcgZXF1YXRpb24pICIuLi5tb3JlIHJlYWxpc3RpY2FsbHksIHRoZSBldGhhbm9sIHlpZWxkIGlzIG1vcmUgbGlrZSAwLjQ2IGcgYW5kIGNhcmJvbiBkaW94aWRlIDAuNDQgZyBmcm9tIDEgZyBzdWdhciIgIChwLiAxMzcgaW4gQnJld2luZyBNYXRlcmlhbHMgYW5kIFByb2Nlc3NlczogQSBQcmFjdGljYWwgQXBwcm9hY2ggdG8gQmVlciBFeGNlbGxlbmNlLCBFZGl0ZWQgYnkgQ2hhcmxlcyBCYW1mb3J0aCBBY2FkZW1pYyBQcmVzcywgMjAxNikNCmBgYHtyfQ0KRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlIDwtIEZQSGNhbGMkZGVsdGEuQUJXKigwLjQ0LzAuNDYpDQojI2NvbnZlcnQgY2FsY0NPMl9pbmNyZWFzZSAoaW4gZy8xMDBtTCkgdG8gY2FsY3VsYXRlZCBDTzIgdm9sdW1lcyBhZGRlZA0KIyMgZy9MID0gMTAqIGcvMTAwbUwNCiMjIFRoZSBjb252ZXJzaW9uIGZhY3RvciBmcm9tIHZvbHVtZXMgb2YgQ08yIHRvIENPMiBieSB3ZWlnaHQgKGcvTCkgaXMgMS45Ni4gRm9yIGV4YW1wbGU6IDIuNSB2b2x1bWVzIHggMS45NiA9IDQuOSBnL2wuDQpGUEhjYWxjJGNhbGNDTzJ2b2xzX2luY3JlYXNlIDwtIEZQSGNhbGMkY2FsY0NPMl9pbmNyZWFzZSoxMC8xLjk2DQpgYGANCg0KDQoNCiMjIGRlZmluZSAiRlBIIiBhcyBhbW91bnQgcHJvZHVjZWQgcGVyIGFtb3VudCBkcnktaG9wcyBhZGRlZCAoYWxsIGluIGcvMTAwbUwpOg0KYGBge3J9DQojIyAgRlBIID0gRm9sZCBQcm9kdWN0aW9uIGR1ZSB0byBIb3BzIChmb2xkLWluY3JlYXNlIGJ5IG1hc3M6IGFtb3VudCBvZiBnaXZlbiBlbmRwb2ludCByZWxhdGl2ZSB0byBhbW91bnQgb2YgaG9wcyBhZGRlZCkNCiMjICANCkZQSGNhbGMkRlBIX0V0T0ggPSBGUEhjYWxjJGRlbHRhLkFCVy9GUEhjYWxjJGhvcHNfZ18xMDBtTA0KRlBIY2FsYyRGUEhfQ08yID0gRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlL0ZQSGNhbGMkaG9wc19nXzEwMG1MDQpGUEhjYWxjJEZQSF9wbGF0byA9IEZQSGNhbGMkZGVsdGEucGxhdG8vRlBIY2FsYyRob3BzX2dfMTAwbUwNCg0KIyMgcmVwbGFjaW5nIHRpbWUtemVybyB0aW1lIHZhbHVlcyB3aXRoIGEgdmVyeSBzbWFsbCBudW1iZXIgKHJhdGhlciB0aGFuIGV4YWN0bHkgemVybykgd2lsbCBwcmV2ZW50IGlzc3VlcyB3aXRoIGFuYWx5c2lzIG9mIG5vbmxpbmVhciBtb2RlbHMNCkZQSGNhbGNbRlBIY2FsYyRkYXlzb25ob3BzPT0wLF0kZGF5c29uaG9wcyA8LSAwLjAxDQoNCg0KDQojIyBhbmQgc2F2ZSB0aGUgdHJhbnNmb3JtZWQgZGF0YSB0byBjc3Y6DQp3cml0ZS5jc3YoRlBIY2FsYywiRlBIY2FsYy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCkZQSGNhbGMgPC0gcmVhZC5jc3YoIkZQSGNhbGMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUpDQoNCmRmPC0gRlBIY2FsYyAlPiUgZ3JvdXBfYnkoRVhQVG5ldykgJT4lDQogIHN1bW1hcmlzZV9hdCh2YXJzKGhvcHNfZ18xMDBtTCxwb3VuZHNfYmJsLGRlbHRhLkFCViwgZGVsdGEuQUJXLCBkZWx0YS5wbGF0bywgRlBIX3BsYXRvLCBGUEhfRXRPSCwgRlBIX0NPMiwgY2FsY0NPMnZvbHNfaW5jcmVhc2UpLGZ1bnMocm91bmQobWVhbiguKSwgMikpKSAlPiUNCiAgYXJyYW5nZShkZXNjKEZQSF9FdE9IKSkgDQpkZg0KYGBgDQoNCg0KDQoNCg0KDQojIGludGVybWlzc2lvbj8NCg0KDQoNCg0KDQoNCiMgdmlzdWFsaXplDQojIyB2ZWN0b3J+dmVjdG9yKnZlY3RvciBwbG90cw0KYGBge3IgcmVncmVzc2lvbi4yfQ0KZGYgPC1GUEhjYWxjDQpwMTwtIGdncGxvdChkZiwgYWVzKHk9RlBIX0V0T0gseD1PRSwgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAyPC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUFERiwgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAzPC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUFCVywgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnA0PC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUVhLCBjb2xvcj1jb250YWN0X2RheXMpKSArIGdlb21fcG9pbnQoc2l6ZT0yKQ0KZ3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBuY29sID0gMikNCmBgYA0KDQoNCg0KIyMgeH55Kig0IHZlY3RvcnMpIGJ5IGNvbG9yDQpgYGB7ciByZWdyZXNzaW9uLjRwbG90LmNvbG9yfQ0KZGYgPC0gRlBIY2FsYw0KeDwtIGRmJEVhDQp5PC0gZGYkQUJXICAgICNGUEhfRXRPSA0KDQpwMTwtIGdncGxvdChkZiwgYWVzKHgseSwgY29sb3I9Zm9ybV9vZl9ob3BzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAyPC0gZ2dwbG90KGRmLCBhZXMoeCx5LCBjb2xvcj1icmV3X2RhdGUpKSArIGdlb21fcG9pbnQoc2l6ZT0yKQ0KcDM8LSBnZ3Bsb3QoZGYsIGFlcyh4LHksIGNvbG9yPXJvdXNlKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnA0PC0gZ2dwbG90KGRmLCBhZXMoeCx5LCBjb2xvcj1wb3VuZHNfYmJsKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBwNCwgbmNvbCA9IDIpDQpgYGANCg0KIyMgM0QgcGxvdHMgd2l0aCBsaWJyYXJ5KHBsb3RseSkNCmBgYHtyfQ0KIyBpbnRlcmFjdGl2ZSAzRCBwbG90cyB3aXRoIGxpYnJhcnkocGxvdGx5KQ0KcGxvdF9seShkYXRhID0gZGYsIHogPSBkZiRQTEFUTy5ob3Bkcm9wLCB4ID0gfmRmJGRheXNvbmhvcHMsIHkgPSAgZGYkQUJXKQ0KYGBgDQoNCiMjIEFtdW5hdGVndWkgIlVzaW5nIENvcnJlbGF0aW9ucyBUbyBVbmRlcnN0YW5kIFlvdXIgRGF0YSINCmBgYHtyIEFtdW5hdGVndWl9DQpteWRhdGE8LUZQSGNhbGMgJT4lIGRwbHlyOjpzZWxlY3QoZXhwdCx2YXJpZXR5LGZvcm1fb2ZfaG9wcyxwb3VuZHNfYmJsLHNwZWNpYWxfY29uZGl0aW9ucyxyb3VzZSxkYXlzb25ob3BzLERIX3RlbXAsYnJld19kYXRlLEFCVyxTRyxPRSxBREYsUkRGLGNhbGNDTzJ2b2xzX2luY3JlYXNlLEZQSF9DTzIsIEZQSF9FdE9IKSAgIyMgbGFzdCBvbmUgaXMgMTAwJSBpbiBqDQoNCiMjIG5vdGUgdXNlIG9mICJkcGx5cjo6c2VsZWN0IiBiZWNhdXNlIG9uZSBvZiB0aGVzZSBwYWNrYWdlcyBpcyBjb25mbGljdGluZyB3aXRoIGRwbHlyIGNvbW1hbmRzIDooKQ0KDQojIyBmb2xsb3dpbmcgIE1hbnVlbCBBbXVuYXRlZ3VpICBodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWlnUFEtcEk4QmpvDQojIyBVc2luZyBDb3JyZWxhdGlvbnMgVG8gVW5kZXJzdGFuZCBZb3VyIERhdGE6IE1hY2hpbmUgTGVhcm5pbmcgV2l0aCBSIA0KIyNmdW5jdGlvbnMgZm9yIGZsYXR0ZW5TcXVhcmVNYXRyaXgNCmNvci5wcm9iIDwtIGZ1bmN0aW9uIChYLCBkZnI9bnJvdyhYKSAtMikgew0KICBSPC0gY29yKFgsIHVzZT0icGFpcndpc2UuY29tcGxldGUub2JzIikNCiAgYWJvdmU8LSByb3coUikgPCBjb2woUikNCiAgcjIgPC0gUlthYm92ZV1eMg0KICBGc3RhdDwtIHIyICogZGZyLygxLXIyKQ0KICBSW2Fib3ZlXSA8LSAxLSBwZihGc3RhdCwgMSwgZGZyKQ0KICBSW3JvdyhSKSA9PSBjb2woUildIDwtIE5BDQogIFINCn0NCmZsYXR0ZW5TcXVhcmVNYXRyaXggPC0gZnVuY3Rpb24obSkgew0KICBpZiggKGNsYXNzKG0pICE9ICJtYXRyaXgiKSB8IChucm93KG0pIT1uY29sKG0pKSkgc3RvcCgiTXVzdCBiZSBhIHNxdWFyZSBtYXRyaXguIikNCiAgaWYoIWlkZW50aWNhbChyb3duYW1lcyhtKSwgY29sbmFtZXMobSkpKSBzdG9wKCJSb3cgYW5kIGNvbHVtbiBuYW1lcyBtdXN0IGJlIGVxdWFsLiIpDQogIHV0IDwtIHVwcGVyLnRyaShtKQ0KICBkYXRhLmZyYW1lKGkgPSByb3duYW1lcyhtKVtyb3cobSlbdXRdXSwNCiAgICAgICAgICAgICBqID0gcm93bmFtZXMobSlbY29sKG0pW3V0XV0sDQogICAgICAgICAgICAgY29yPXQobSlbdXRdLA0KICAgICAgICAgICAgIHA9bVt1dF0pDQp9DQoNCiMjIGxpYnJhcnkoY2FyZXQpIHRvIGR1bW1pZnkgZXZlcnl0aGluZyAodHVybiBhbGwgY2hhcmFjdGVycyZmYWN0b3JzIGludG8gY29sdW1uczsgIGlnbm9yZXMgbnVtYmVycyBhbmQgaW50ZWdlcnMpDQpkbXk8LSBkdW1teVZhcnMoIiB+IC4iLGRhdGEgPSBteWRhdGEpDQpteWR1bW1pZmllZGRhdGE8LSBkYXRhLmZyYW1lKHByZWRpY3QoZG15LCBuZXdkYXRhID0gbXlkYXRhKSkNCmNvck1hdCA9IGNvcihteWR1bW1pZmllZGRhdGEpDQpjb3JNYXN0ZXJMaXN0PC0gZmxhdHRlblNxdWFyZU1hdHJpeChjb3IucHJvYihteWR1bW1pZmllZGRhdGEpKSAjIyBsaXN0IG9mIGFsbCBjb3JyZWxhdGlvbnMNCiMjIG9yZGVyIGJ5IHN0cmVuZ3RoIG9mIGNvcnJlbGF0aW9uDQpjb3JsaXN0PC0gY29yTWFzdGVyTGlzdCAlPiUgYXJyYW5nZSgtYWJzKGNvck1hc3Rlckxpc3QkY29yKSkgDQp3cml0ZS5jc3YoY29ybGlzdCxwYXN0ZTAoIkZMQVQgY29ycmVsYXRpb24gbWF0cml4Xy5jc3YiKSkNCmNvcmxpc3QgPC0gY29ybGlzdCAlPiUgZHBseXI6OmZpbHRlcihqPT0iRlBIX0V0T0giKSAgICMjIGZpbHRlciBzcGVjaWZpYyBlbmRwb2ludA0KaGVhZChjb3JsaXN0LDUwKQ0KYGBgDQoNCiMjIHBhaXJzLnBhbmVscyBjb3JyZWxhdGlvbiBtYXRyaXggZnJvbSBsaWJyYXJ5KHBzeWNoKQ0KYGBge3J9DQojIyBzcGVjaWZ5IGludGVyZXN0aW5nIHZhcmlhYmxlczogIA0KaW50ZXJlc3Rpbmd2YXJpYWJsZXM8LWMoIkFCVyIsICJwb3VuZHNfYmJsIiwgImRheXNvbmhvcHMiLCAiY2FsY0NPMnZvbHNfaW5jcmVhc2UiKSANCnBhaXJzLnBhbmVscyhteWR1bW1pZmllZGRhdGFbYyhpbnRlcmVzdGluZ3ZhcmlhYmxlcywgIkZQSF9FdE9IIildKQ0KYGBgDQoNCg0KI21vZGVsDQpPYnNlcnZpbmcgdGhlIGltcGFjdHMgb2YgZHJ5aG9wcGluZyBpbiB0aGUgcHJlc2VuY2Ugb2YgbGl2ZSB5ZWFzdCBoYXMgbGVkIG1hbnkgYnJld2luZyBwcm9mZXNzaW9uYWxzIHRvIHVuZGVyc3RhbmQgdGhhdCBGUEggaXMgYSBmdW5jdGlvbiBvZiBtYW55IG9mIHRoZSB2YXJpYWJsZXMgYWJvdmUgaW5jbHVkaW5nIGhvcCB2YXJpZXR5LGZvcm1fb2ZfaG9wcyxoYXJ2ZXN0WWVhcixPWCxESF90ZW1wLGRheXNvbmhvcHMscm91c2UscG91bmRzX2JibC4uLi4gTWFueSBoYXZlIGludHVpdGl2ZWx5IGNyZWF0ZWQgYSBtb2RlbCBpbiB0aGVpciBoZWFkcyAod2l0aG91dCBuZWNlc3NhcmlseSB0aGlua2luZyBvZiBpdCBhcyBzdWNoKSBhbmQgc2tpbGxmdWxseSBhZGp1c3QgcHJvY2VzcyB3aGVuIG5lY2Vzc2FyeSB0byBhY2NvdW50IGZvciB0aGlzIHBoZW5vbWVub24uICBJbiBsaW5lYXIgbW9kZWxpbmcsIG91ciBmdW5jdGlvbiB3aWxsIHRha2Ugb24gdGhlIGZvcm06DQokRlBIID0gIGludGVyY2VwdCArIFxiZXRhX3sxfVhfezF9ICsgXGJldGFfezJ9WF97Mn0gKyAuLi4gKyBcYmV0YV97bn1YX3tufSQgd2hlcmUgJFxiZXRhJCB2YWx1ZXMgYXJlIHdoYXQgd2UncmUgYXR0ZW1wdGluZyB0byBkZXJpdmUgaW4gdGhpcyBtb2RlbGluZyBleGVyY2lzZSwgYW5kIFggdmFsdWVzIGFyZSAoY29sbGVjdGl2ZWx5KSBhIHBhcnRpY3VsYXIgc2V0IG9mIGNvbmRpdGlvbnMuDQoNCj4+IEFjdGlvbkl0ZW06ICBhZGQgbGluayB0byBtb2RlbGluZyBvdmVydmlldw0KDQoNCiMjIGxpbmVhciBtb2RlbHMgMSAoY3JlYXRlIG1vZGVscykNCmBgYHtyfQ0KZGY8LUZQSGNhbGMNCg0KbG0xPC1sbShkZiRTR35kZiRPRSkNCmxtMjwtbG0oZGYkU0d+ZGYkQURGKQ0KbG0zPC1sbShkZiRTR35kZiRBQlcpDQpsbTQ8LWxtKGRmJFNHfmRmJEVhKQ0KcGFyKG1mcm93ID0gYygyLCAyKSwgb21hID0gYygwLCAwLCAwLCAwKSkNCnBsb3QoZGYkU0d+ZGYkT0UpDQphYmxpbmUobG0xKQ0KcGxvdChkZiRTR35kZiRBREYpDQphYmxpbmUobG0yKQ0KcGxvdChkZiRTR35kZiRBQlcpDQphYmxpbmUobG0zKQ0KcGxvdChkZiRTR35kZiRFYSkNCmFibGluZShsbTQpDQpgYGANCg0KIyMgbGluZWFyIG1vZGVscyAyICh2aWV3IHJlc2lkdWFsIHBsb3RzKQ0KYGBge3J9DQpkZjwtRlBIY2FsYw0KDQpsbTE8LWxtKGRmJFNHfmRmJE9FKQ0KbG0yPC1sbShkZiRTR35kZiRBREYpDQpsbTM8LWxtKGRmJFNHfmRmJEFCVykNCmxtNDwtbG0oZGYkU0d+ZGYkRWEpDQoNCnBhcihtZnJvdyA9IGMoMiwgMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQpwbG90KGxtMSRyZXNpZHVhbHMpDQpwbG90KGxtMiRyZXNpZHVhbHMpDQpwbG90KGxtMyRyZXNpZHVhbHMpDQpwbG90KGxtNCRyZXNpZHVhbHMpDQojc3VtbWFyeShsbTQpDQpgYGANCg0KDQojIyBjb21wYXJlIGxpbmVhciBtb2RlbHMgdXNpbmcgbWVtaXNjIHBhY2thZ2UNCmBgYHtyfQ0KZGY8LUZQSGNhbGMNCg0KbG0xPC1sbShkZiRTR35kZiRPRSkNCmxtMjwtbG0oZGYkU0d+ZGYkQURGKQ0KbG0zPC1sbShkZiRTR35kZiRBQlcpDQpsbTQ8LWxtKGRmJFNHfmRmJEVhKQ0KDQptdGFibGUxMjM0IDwtIG10YWJsZSgiTW9kZWwgMSI9bG0xLCJNb2RlbCAyIj1sbTIsIk1vZGVsIDMiPWxtMywgIk1vZGVsIDQiPWxtNCwNCiAgICAgICAgICAgICAgICAgICAgc3VtbWFyeS5zdGF0cz1jKCJzaWdtYSIsIlItc3F1YXJlZCIsIkYiLCJwIiwiTiIpLHNob3cuZXFuYW1lcz1UKQ0KDQptdGFibGUxMjM0YiA8LSByZWxhYmVsKG10YWJsZTEyMzQsDQogICAgICAgICAgICAgICAgICAgICAgIihJbnRlcmNlcHQpIiA9ICJDb25zdGFudCIsDQogICAgICAgICAgICAgICAgICAgICAgeDEgPSAiT0UgPSBPcmlnaW5hbCBFeHRyYWN0IChnLzEwMG1MKSIsDQogICAgICAgICAgICAgICAgICAgICAgeDIgPSAiQURGID0gQXBwYXJlbnQgRGVncmVlIG9mIEZlcm1lbnRhdGlvbiAoJSkiLA0KICAgICAgICAgICAgICAgICAgICAgIHgzID0gIkFCVyA9IEV0aGFub2wgKHcvdykiLA0KICAgICAgICAgICAgICAgICAgICAgIHg0ID0gIkVyID0gUmVzaWR1YWwgRXh0cmFjdCAoZy8xMDBtTCkiDQogICAgICAgICAgICAgICAgICAgICAgKQ0KbXRhYmxlMTIzNA0KI3Nob3dfaHRtbChtdGFibGUxMjM0YikNCg0KDQpgYGANCg0KDQoNCg0KDQojIyBjb21wYXJlIG1vcmUgbGluZWFyIG1vZGVscw0KYGBge3J9DQpkZiA8LSBGUEhjYWxjDQpsbTE8LWxtKEZQSF9FdE9IfmRheXNvbmhvcHMsIGRhdGE9ZGYpDQpsbTI8LWxtKEZQSF9FdE9IfmRheXNvbmhvcHMqZm9ybV9vZl9ob3BzLCBkYXRhPWRmKQ0KbG0zPC1sbShGUEhfRXRPSH5kYXlzb25ob3BzKnJvdXNlLCBkYXRhPWRmKQ0KbG00PC1sbShGUEhfRXRPSH5kYXlzb25ob3BzKmZvcm1fb2ZfaG9wcypyb3VzZSwgZGF0YT1kZikNCg0KbXRhYmxlMTIzNCA8LSBtdGFibGUoIk1vZGVsIDEiPWxtMSwiTW9kZWwgMiI9bG0yLCJNb2RlbCAzIj1sbTMsICJNb2RlbCA0Ij1sbTQsDQogICAgICAgICAgICAgICAgICAgIHN1bW1hcnkuc3RhdHM9Yygic2lnbWEiLCJSLXNxdWFyZWQiLCJGIiwicCIsIk4iKSxzaG93LmVxbmFtZXM9VCkNCm10YWJsZTEyMzRiIDwtIHJlbGFiZWwobXRhYmxlMTIzNCwNCiAgICAgICAgICAgICAgICAgICAgICAiKEludGVyY2VwdCkiID0gIkNvbnN0YW50IiwNCiAgICAgICAgICAgICAgICAgICAgICBTRyA9ICJTcGVjaWZpYyBHcmF2aXR5IiwNCiAgICAgICAgICAgICAgICAgICAgICBBQlcgPSAiQUJXID0gRXRoYW5vbCAody93KSIsDQogICAgICAgICAgICAgICAgICAgICAgRXIgPSAiRXIgPSBSZXNpZHVhbCBFeHRyYWN0IChnLzEwMG1MKSINCiAgICAgICAgICAgICAgICAgICAgICApDQptdGFibGUxMjM0DQojc2hvd19odG1sKG10YWJsZTEyMzRiKQ0KYGBgDQoNCg0KDQojIyBUaGUgUiBCb29rIChDcmF3bGV5KSBUYWJsZSAyMC4xOiBub25saW5lYXIgZnVuY3Rpb25zIHVzZWZ1bCBpbiBiaW9sb2d5ICAgDQpUYWJsZSAyMC4xLiBbVXNlZnVsIG5vbi1saW5lYXIgZnVuY3Rpb25zXShodHRwczovL3d3dy5jcy51cGMuZWR1L35yb2JlcnQvdGVhY2hpbmcvZXN0YWRpc3RpY2EvVGhlUkJvb2sucGRmICJNaWNoYWVsIEouIENyYXdsZXkuICBUaGUgUiBib29rLiAgcC4gNzM4IikgRVhQQU5ERUQ6DQoNCg0KDQp8IEZ1bmN0aW9uIENsYXNzIHwgbmFtZSB8IGVxdWF0aW9uIHwgZXhhbXBsZSBjb2RlIHxleGFtcGxlIGFwcGxpY2F0aW9uc3wNCnw6LS0tLS0tLS0tLXw6LS0tLS06fDotLS0tLS0tfDotLS0tLXw6LS0tLS18DQp8ICoqQXN5bXB0b3RpYyBmdW5jdGlvbnMqKnxNaWNoYWVsaXPigJNNZW50ZW58JHkgPVxmcmFje2F4fXsxK2J4fSR8bmxzKGJvbmV+YSphZ2UvKDErYiphZ2UpLHN0YXJ0PWxpc3QoYT04LGI9MC4wOCkpKSB8IGVuenltZSByZWFjdGlvbnMgfA0KfCB8IHwgfCBubHMocmF0ZX5TU21pY21lbihjb25jLGEsYikpIHwgdGJkfA0KfCB8Mi1wYXJhbWV0ZXIgYXN5bXB0b3RpYyBleHBvbmVudGlhbCB8ICR5ID0gYSgxIOKIkiBlXnviiJJieH0gKSQgfG5scyhib25lfmEqKDEtZXhwKC1jKmFnZSkpLHN0YXJ0PWxpc3QoYT0xMjAsYz0wLjA2NCkpIHwgdGJkIHwNCnwgfDMtcGFyYW1ldGVyIGFzeW1wdG90aWMgZXhwb25lbnRpYWwgfCAkeSA9IGEg4oiSIGJlXnviiJJjeH0kIHwgbmxzKGJvbmV+YS1iKmV4cCgtYyphZ2UpLHN0YXJ0PWxpc3QoYT0xMjAsYj0xMTAsYz0wLjA2NCkpIHwgdGJkIHwNCnwgfCB8IHwgbmxzKGJvbmV+U1Nhc3ltcChhZ2UsYSxiLGMpKSB8IHRiZCB8DQp8IHwgfCB8IG5scyhkZW5zaXR5IH4gU1Nsb2dpcyhsb2coY29uY2VudHJhdGlvbiksIGEsIGIsIGMpKSB8IHRiZCB8DQp8KipTLXNoYXBlZCBmdW5jdGlvbnMqKiB8Mi1wYXJhbWV0ZXIgbG9naXN0aWMgfCR5ID0gXGZyYWN7ZV57YStieH19ezEgKyBlXnthK2J4fX0kIHwgfCB0YmQgfA0KfCB8IDMtcGFyYW1ldGVyIGxvZ2lzdGljIHwgJHkgPSBcZnJhY3thfXsxICsgYmVee+KIkmN4fX0kIHwgfCB0YmQgfA0KfCB8IDQtcGFyYW1ldGVyIGxvZ2lzdGljIHwgJHkgPSBhICsgXGZyYWN7Yi1hfXsxICsgZV57KGPiiJJ4KS9kfX0kIHwgbmxzKHdlaWdodH5TU2ZwbChUaW1lLCBhLCBiLCBjLCBkKSkgfCB0YmQgfA0KfCB8IFdlaWJ1bGwgfCAkeSA9IGEg4oiSIGJlXnviiJIoY3heZCl9JCB8IG5scyh3ZWlnaHQgfiBTU3dlaWJ1bGwodGltZSwgQXN5bSwgRHJvcCwgbHJjLCBwd3IpKSB8IHRiZCB8DQp8IHwgR29tcGVydHogfCAkeSA9IGFlXnviiJJiZV574oiSY3h9fSQgfCB8IHRiZCB8DQp8ICoqSHVtcGVkIGN1cnZlcyoqIHwgUmlja2VyIGN1cnZlIHwgJHkgPSBheGVee+KIkmJ4fSQgfCB8IHRiZCB8DQp8IHwgRmlyc3Qtb3JkZXIgY29tcGFydG1lbnQgfCAkeSA9IGsgZXhwKOKIkmV4cChhKXgpIOKIkiBleHAo4oiSZXhwKGIpeCkkIHwgbmxzKGNvbmN+U1Nmb2woRG9zZSwgVGltZSwgYSwgYiwgYykpIHwgdGJkIHwNCnwgfCBCZWxsLXNoYXBlZCB8ICR5ID0gYSBleHAo4oiSQUJTKGJ4KV4yKSQgfCB8IHRiZCB8DQp8IHwgQmlleHBvbmVudGlhbCB8ICR5ID0gYWVee2J4fSDiiJIgY2Vee+KIkmR4fSQgIHwgfCB0YmQgfA0KDQoNCg0KIyMgTWljaGFlbGlzLU1lbnRvbiBtb2RlbCAoQ3Jhd2xleSBwLiA3NDEpDQokeSA9XGZyYWN7YXh9ezErYnh9JA0KDQpgYGB7cn0NCmV4cHQyIDwtIEZQSGNhbGMgJT4lIGRwbHlyOjpmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBkcGx5cjo6c2VsZWN0KEZQSF9FdE9ILCByb3VzZSwgZGF5c29uaG9wcyxicmV3X2RhdGUsIHBvdW5kc19iYmwsZm9ybV9vZl9ob3BzLEFCVy5jb250cm9sX05ILGRheW9mYWRkaXRpb24pDQoNCm15bW9kZWwgPC0gbmxzKEZQSF9FdE9IfmEqZGF5c29uaG9wcy8oMStiKmRheXNvbmhvcHMpLCBkYXRhPWV4cHQyLHN0YXJ0PWxpc3QoYT04LCBiPTAuMDgpKQ0KDQpzdW1tYXJ5KG15bW9kZWwpDQoNCmBgYA0KDQoNCg0KYGBge3J9DQp4IDwtIHNlcSgwLDUwLDAuMSkNCnl2IDwtIHByZWRpY3QobXltb2RlbCwgbGlzdChkYXlzb25ob3BzPXgpKQ0Ke3Bsb3QoZXhwdDIkZGF5c29uaG9wcywgZXhwdDIkRlBIX0V0T0gsIHBjaD0yMSwgY29sPSJwdXJwbGUiLCBiZz0iZ3JlZW4iKQ0KICBsaW5lcyh4LHl2LGNvbD0iYmx1ZSIpfQ0KDQpgYGANCg0KDQpTbyB1c2luZyB0aGUgTWljaGFlbGlzLU1lbnRlbiBlcXVhdGlvbiBhcyBvdXIgbW9kZWwsIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBob3AtaW5kdWNlZCBldGhhbm9sIHByb2R1Y3Rpb24gKEZQSF9FdE9IKSBhbmQgaG9wIGNvbnRhY3QgdGltZSAoZGF5c29uaG9wcykgd291bGQgYmUgZXhwcmVzc2VkIGFzOg0KJEZQSF9FdE9IID0gXGZyYWN7MC4xODM1NzIqZGF5c29uaG9wc317MSswLjA2NjM4OCpkYXlzb25ob3BzfSQNCg0KDQoNCiMjIG5vbmxpbmVhciBtb2RlbHMgb2YgaG9wLWluZHVjZWQgUGxhdG8gZHJvcCBvdmVyIHRpbWUgd2l0aCBsaWJyYXJ5KGRyYykgDQpgYGB7ciBkcmMubW9kZWxzfQ0KZGYgPC0gRlBIY2FsYyAlPiUgZmlsdGVyKGV4cHQ9PSIgMkEiKSANCmRmJFBMQVRPLmhvcGRyb3AgPC0gZGYkaW5pdGlhbF9wbGF0byArIGRmJGRlbHRhLnBsYXRvDQoNCnggPC0gZGYkZGF5c29uaG9wcw0KeSA8LSBkZiRQTEFUTy5ob3Bkcm9wDQpncm91cDwtIGFzLmZhY3RvcihkZiRzcGVjaWFsX2NvbmRpdGlvbnMpICAgI3JvdXNlDQpjb2xzIDwtIGFzLm51bWVyaWMoZ3JvdXApDQpsZWdlbmQuY29scyA8LSBhcy5udW1lcmljKGFzLmZhY3RvcihsZXZlbHMoZ3JvdXApKSkNCg0KIyMgRml0dGluZyBtb2RlbHMgdXNpbmcgZnVuY3Rpb24gZHJtIGZyb20gbGlicmFyeShkcmMpLiBzZWUgPGh0dHA6Ly9yc3RhdHM0YWcub3JnL2Rvc2UtcmVzcG9uc2UtY3VydmVzLmh0bWw+IGZvciBvdmVydmlldyBhbmQgPGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9kcmMvPiBmb3IgbGlzdCBvZiBtb2RlbHMgKGZjdCB2YWx1ZXMpIGFuZCBvdGhlciBmdW5jdGlvbnMgYXZhaWxhYmxlIGluIGRyYw0KDQojIyBjcmVhdGUgbW9kZWxzDQptLkxMLjM8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMTC4zKCkpICMzLXBhcmFtZXRlciBsb2dpc3RpYyAobG93ZXIgbGltaXQgYXQgMCkNCiNtLkxMLjN1PC1kcm0oeSB+eCwgZmN0ID0gTEwuM3UoKSkgIzMtcGFyYW1ldGVyIGxvZ2lzdGljICh1cHBlciBsaW1pdCBhdCAxKQ0KbS5MTC40PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTEwuNCgpKSAjNC1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljICANCiMgZnJvbSA/P0xMLjQ6ICBmKHgpID0gYyArIFxmcmFje2QtY317MStcZXhwKGIoXGxvZyh4KS1cbG9nKGUpKSl9IG9yIGluIGFub3RoZXIgcGFyYW1ldGVyaXNhdGlvbiAoY29udmVydGluZyB0aGUgdGVybSBcbG9nKGUpIGludG8gYSBwYXJhbWV0ZXIpIGYoeCkgPSBjICsgXGZyYWN7ZC1jfXsxK1xleHAoYihcbG9nKHgpLVx0aWxkZXtlfSkpfSAgDQptLkwuNCA8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMLjQoKSkgI2NoYW5naW5nIHRoZSBmY3QgPSBMTC40KCkgdG8gZmN0ID0gTC40KCkgYWxsb3dzIHBsb3R0aW5nIG9uIGEgbG9nMTAgc2NhbGUNCm0uTEwyLjQ8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMTDIuNCgpKSAjNC1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIHdpdGggbG9nKGUpIHJhdGhlciB0aGFuIGUgYXMgYSBwYXJhbWV0ZXIgIA0KbS5MTDIuNTwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IExMMi41KCkpICNHZW5lcmFsaXNlZCBsb2ctbG9naXN0aWMNCiMgZnJvbSA/P0xMLjIuNTogICBmKHgpID0gYyArIFxmcmFje2QtY317KDErXGV4cChiKFxsb2coeCktZSkpKV5mfQ0KbS5MTC41PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTEwuNSgpKSAjNS1wYXJhbWV0ZXIgbG9naXN0aWMgIA0KbS5XMS40PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gVzEuNCgpKSAjNC1wYXJhbWV0ZXIgV2VpYnVsbDEgIA0KbS5XMi40PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gVzIuNCgpKSAjNC1wYXJhbWV0ZXIgV2VpYnVsbDIgIA0KbS5CQy41PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gQkMuNSgpKSAjNS1wYXJhbWV0ZXIgQnJhaW4tQ291c2VucyAoaG9ybWVzaXMpDQptLkFSLjM8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBBUi4zKCkpICMzLXBhcmFtZXRlciBTaGlmdGVkIGFzeW1wdG90aWMgcmVncmVzc2lvbg0KI20uTU0uMjwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IE1NLjIoKSkgIzItcGFyYW1ldGVyIE1pY2hhZWxpcy1NZW50ZW4NCm0uTU0uMzwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IE1NLjMoKSkgIzMtcGFyYW1ldGVyIE1pY2hhZWxpcy1NZW50ZW4NCg0KDQojI3Bsb3QNCiNwYXIobWZyb3cgPSBjKDEsMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQoNCntwbG90KHggLCB5LCBjb2w9Y29scywgbWFpbj0icmF3IGRhdGEiKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZXZlbHMoZ3JvdXApLCBwY2g9MTYsIGNvbD1sZWdlbmQuY29scyl9DQoNCnBsb3QobS5MTC4zLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJMTC4zIChsb3dlciBsaW1pdCBhdCAwKSIpDQpwbG90KG0uTEwuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTEwuNCBmb3VyLXBhcmFtZXRlciBsb2ctbG9naXN0aWMiKQ0KcGxvdChtLkwuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTC40IikNCnBsb3QobS5MTC41LCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJMTC41IikNCnBsb3QobS5MTDIuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTEwyLjQgZm91ci1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIikNCnBsb3QobS5MTDIuNSwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iR2VuZXJhbGlzZWQgbG9nLWxvZ2lzdGljIikNCnBsb3QobS5XMS40LCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJXZWlidWxsMSIpDQpwbG90KG0uVzIuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iV2VpYnVsbDIiKQ0KcGxvdChtLkJDLjUsIHR5cGU9J2FsbCcsY29sPWNvbHMsIG1haW49IkJyYWluLUNvdXNlbnMgKGhvcm1lc2lzKSIpDQpwbG90KG0uQVIuMywgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iU2hpZnRlZCBhc3ltcHRvdGljIHJlZ3Jlc3Npb24iKQ0KI3Bsb3QobS5NTS4yLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSIyLXBhcmFtZXRlciBNaWNoYWVsaXMtTWVudGVuIikNCnBsb3QobS5NTS4zLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSIzLXBhcmFtZXRlciBNaWNoYWVsaXMtTWVudGVuIikNCg0KYGBgDQoNCg0KDQojIyAgU3BlZXJzMjAwMzogTm9u4oCQTGluZWFyIE1vZGVsbGluZyBvZiBJbmR1c3RyaWFsIEJyZXdpbmcgRmVybWVudGF0aW9ucw0KUi4gQWxleCBTcGVlcnMsIFBldGVyIFJvZ2VycywgQnJ1Y2UgU21pdGgNCkouIEluc3QuIEJyZXcuIDEwOSgzKSwgMjI54oCTMjM1LCAyMDAzIA0KPGh0dHBzOi8vZG9pLm9yZy8xMC4xMDAyL2ouMjA1MC0wNDE2LjIwMDMudGIwMDE2My54Pg0KRnJlZSBBY2Nlc3MgZnJvbSB0aGUgSW5zdGl0dXRlIG9mIEJyZXdpbmchDQo8aHR0cHM6Ly9vbmxpbmVsaWJyYXJ5LndpbGV5LmNvbS9kb2kvMTAuMTAwMi9qLjIwNTAtMDQxNi4yMDAzLnRiMDAxNjMueD4NCg0KRm9sbG93aW5nIFNwZWVyczIwMDMsIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBwbGF0byBhbmQgdGltZSBpbiBwcmltYXJ5IGZlcm1lbnRhdGlvbnMgaXMgd2VsbCBtb2RlbGVkIGJ5IGEgZm91ciBwYXJhbWV0ZXIgbG9naXN0aWMgbW9kZWw6DQoNClBMQVRPID0gUElORiArIChQX0QgLyAoMSsgKEVYUCgtQiooVElNRS1NKSkpKSkNCg0Kb3INCg0KJFBMQVRPID0gUF97aW5mfSsgXGZyYWN7UF9EfXsxK2Veey1CKHQtTSl9fSQNCg0Kd2hlcmUNCg0KKiB0ID0gdGltZSBpbnRvIGZlcm1lbnRhdGlvbg0KKiBQX0QgPSAgY2hhbmdlIGluIFBsYXRvIGR1cmluZyBmZXJtZW50YXRpb24gKE9FIC0gUF97aW5mfSkNCiogQiB+IG1heGltdW0gZmVybWVudGF0aW9uIHJhdGUNCiogTSA9IGZlcm1lbmF0aW9uIG1pZHBvaW50DQoqIFBfaW5mID0gZmluYWwgZ3Jhdml0eQ0KDQoNCnNvIHRoZW4gZm9yIG91ciBjYXNlDQoNCiRQTEFUTyA9IFBfe2luZn0rICBcZnJhY3tPRS1QX3tpbmZ9fXsxK2Veey1GX3ttYXh9KGRheXNvbmhvcHMtTSl9fSQNCg0KUExBVE8gPSBQSU5GICsgKFAwIC8gKDErIChFWFAoQiooSE9VUlMtTSkpKSkpDQoNCg0KIyMgIE1hY0ludG9zaDIwMTY6IEFuIEV4YW1pbmF0aW9uIG9mIFN1YnN0cmF0ZSBhbmQgUHJvZHVjdCBLaW5ldGljcyBEdXJpbmcgQnJld2luZyBGZXJtZW50YXRpb25zDQpBbmRyZXcgSi4gTWFjSW50b3NoLCBNYXJpYSBKb3NleSwgUi4gQWxleCBTcGVlcnMNCltKLiBBbS4gU29jLiBCcmV3LiBDaGVtLiA3NCg0KSwgMjUwLTI1NywgMjAxNl0oaHR0cHM6Ly93d3cuYXNiY25ldC5vcmcvcHVibGljYXRpb25zL2pvdXJuYWwvdm9sLzIwMTYvUGFnZXMvQVNCQ0otMjAxNi00NzUzLTAxLmFzcHgpDQoNCiJ0aGUgZml2ZS1wYXJhbWV0ZXIgbWV0aG9kIHdhcyBzZWxlY3RlZCBvdmVyIHRoZQ0Kc3RhbmRhcmQgZm91ci1wYXJhbWV0ZXIgbG9naXN0aWMgZGV0YWlsZWQgaW4gQVNCQyBNZXRob2QgWWVhc3QtDQoxNCBiZWNhdXNlIGl0IHlpZWxkZWQgYSBzdXBlcmlvciBmaXQgd2hlbiBhc3Nlc3NlZCB3aXRoIGFuIGV4dHJhDQpzdW0tb2Ytc3F1YXJlcyBGIHRlc3QuIFdlIGZ1cnRoZXIgbm90ZSB0aGF0IGl0IGlzIG9ubHkgc3RhdGlzdGljYWxseQ0KYWR2YW50YWdlb3VzIHRvIGFwcGx5IHRoZSBmaXZlLXBhcmFtZXRlciBtb2RlbCB3aGVuIG1hbnkgZGF0YQ0KcG9pbnRzIGFyZSBhdmFpbGFibGUgKDcyIGZyb20gdGhpcyBleHBlcmltZW50IGFzIG9wcG9zZWQgdG8gMTANCndoZW4gdXNpbmcgWWVhc3QtMTQpLiINCg0KTWFjSW50b3NoOiAgJFBfeyh0KX0gPVxmcmFje1BfaSAtIFBfZX17KDErcyAqIGVeey1CKHQtTSl9KV57MS9zfX0kDQoNCmxpYnJhcnkoZHJjKQ0KZ2V0TWVhbkZ1bmN0aW9ucygpDQoNCiogZHJjIExMLjQ6ICAkZih4KSA9IGMgKyBcZnJhY3tkLWN9ezErXGV4cChiKFxsb2coeCktXHRpbGRle2V9KSl9JCAgIzRwYXJhbSBsb2dpc3RpYw0KKiBkcmMgTEwuNTogICRmKHgpID0gYyArIFxmcmFje2QtY317KDErXGV4cChiKFxsb2coeCktZSkpKV5mfSQgICAgICAjNXBhcmFtIGxvZ2lzdGljDQoqIGRyYyBHLjQgJGYoeCkgPSBjICsgKGQtYykoXGV4cCgtXGV4cChiKHgtZSkpKSkkICAgICAgICAgICAgICAgICAgICM0cGFyYW0gR29tcGVydHogIA0KKiBkcmMgVzEuNCAkZih4KSA9IGMgKyAoZC1jKSBcZXhwKC1cZXhwKGIoXGxvZyh4KS1cbG9nKGUpKSkpJCAgICAgICAjNHBhcmFtIFdlaWJ1bGwxDQoqIGRyYyBXMi40ICRmKHgpID0gYyArIChkLWMpICgxIC0gXGV4cCgtXGV4cChiKFxsb2coeCktXGxvZyhlKSkpKSkkICM0cGFyYW0gV2VpYnVsbDINCg0KDQojIyBtdWx0aWxldmVsIGxvZ2lzdGljIG1vZGVscyB3aXRoIGxpYnJhcnkoZHJjKQ0KYGBge3IgZHJjLm1vZGVscy5sZXZlbHN9DQpkZiA8LSBGUEhjYWxjICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpIA0KZGYkUExBVE8uaG9wZHJvcCA8LSBkZiRpbml0aWFsX3BsYXRvICsgZGYkZGVsdGEucGxhdG8NCnggPC0gZGYkZGF5c29uaG9wcw0KeSA8LSBkZiRQTEFUTy5ob3Bkcm9wDQpteWxldmVsczwtIGFzLmZhY3RvcihkZiRzcGVjaWFsX2NvbmRpdGlvbnMpICAjcm91c2UNCmNvbHMgPC0gYXMubnVtZXJpYyhteWxldmVscykNCmxlZ2VuZC5jb2xzIDwtIGFzLm51bWVyaWMoYXMuZmFjdG9yKGxldmVscyhteWxldmVscykpKQ0KDQojIyBjcmVhdGUgbW9kZWxzDQojbS5MTC40PC1kcm0oeSB+eCxteWxldmVscywgZmN0ID0gTEwuNChuYW1lcyA9IGMoIlNsb3BlIiwgIkxvd2VyIiwgIlVwcGVyIiwgIk1pZHBvaW50IG9yIEVENTAiKSkpICM0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgKGdlbmVyYWwgcGFyYW1ldGVycykNCiNtLkxMLjQ8LWRybSh5IH54LG15bGV2ZWxzLCBmY3QgPSBMTC40KG5hbWVzID0gYygiU2xvcGUiLCAiTG93ZXIiLCAiVXBwZXIiLCAiRUQ1MCIpKSkgIzQtcGFyYW1ldGVyIGxvZy1sb2dpc3RpYyAoRG9zZS1SZXNwb25zZSBwYXJhbWV0ZXJzKQ0KbS5MTC40PC1kcm0oeSB+eCxteWxldmVscywgZmN0ID0gTEwuNChuYW1lcyA9IGMoIkZfbWF4IiwgIlBfaW5mIiwgIk9FIiwgIk0iKSkpICM0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgIA0KbS5MLjQgPC1kcm0oeSB+eCxteWxldmVscywgZmN0ID0gTC40KCkpICNjaGFuZ2luZyB0aGUgZmN0ID0gTEwuNCgpIHRvIGZjdCA9IEwuNCgpIGFsbG93cyBwbG90dGluZyBvbiBhIGxvZzEwIHNjYWxlDQptLkxMMi40PC1kcm0oeSB+eCxteWxldmVscywgZmN0ID0gTEwyLjQoKSkgIzQtcGFyYW1ldGVyIGxvZy1sb2dpc3RpYyB3aXRoIGxvZyhlKSByYXRoZXIgdGhhbiBlIGFzIGEgcGFyYW1ldGVyICANCm0uTEwyLjU8LWRybSh5IH54LG15bGV2ZWxzLCBmY3QgPSBMTDIuNSgpKSAjR2VuZXJhbGlzZWQgbG9nLWxvZ2lzdGljDQptLkxMLjU8LWRybSh5IH54LG15bGV2ZWxzLCBmY3QgPSBMTC41KCkpICM1LXBhcmFtZXRlciBsb2dpc3RpYyAgDQoNCm15bW9kZWwgPC0gbS5MTC40DQoNCmNvbmZpbnQobXltb2RlbCkNCg0Ke3Bsb3QobXltb2RlbCxsb2c9IiIsICBicm9rZW4gPSBUUlVFLCBiY29udHJvbCA9IGxpc3Qoc3R5bGUgPSAic2xhc2giKSwgY29sID0gYygyLDYsMywyMyw1NiksIG1haW4gPSAiaW1wYWN0IG9mIG15bGV2ZWxzIG9uIGVzdGltYXRlZCBtb2RlbCBwYXJhbWF0ZXJzIikNCmxlZ2VuZCgzNCwgMy43LCBsZWdlbmQ9Yygicm91c2VkIiwic3RpbGwiKSwgZmlsbD1jKCJyZWQiLCAicGluayIpLCB0aXRsZT0ibXlsZXZlbHMiKX0NCg0KbXltb2RlbA0Kc3VtbWFyeShteW1vZGVsKQ0KDQpgYGANCg0KRWFjaCBsZXZlbCAocm91c2UgYW5kIHN0aWxsKSBoYXMgYSB1bmlxdWUgY3VydmUgc2hhcGUgYW5kIHRoZXJlZm9yZSBkaWZmZXJlbnQgcGFyYW1ldGVycyBmb3IgYSBiZXN0LWZpdCBsb2ctbG9naXN0aWMgbW9kZWwuICBOb3cgdGhhdCB3ZSBrbm93IHRoaXMsIGl0IGlzIHByYWN0aWNhbCB0byBhZGRyZXNzIGVhY2ggc2V0IG9mIGNvbmRpdGlvbnMgc2VwYXJhdGVseSwgYW5kIGJhc2VkIG9uIHRoZSBMTC40IG1vZGVsIGFib3ZlIHdlIGhhdmU6DQoNCmZvciByb3VzZSA9IFRSVUU6DQoNCiRQTEFUT197cm91c2VkfSA9IFBfe2luZn0rICBcZnJhY3tPRS1QX3tpbmZ9fXsxK2Veey1GX3ttYXh9KGRheXNvbmhvcHMtTSl9fSQNCg0Kb3INCg0KJFBMQVRPX3tyb3VzZWR9ICA9IDEuODA5ICsgXGZyYWN7MS45NH17MStlXnstMS4zOTI1KGRheXNvbmhvcHMtMTIuMjcyKX19JA0KDQphbmQNCg0KJFBMQVRPX3tzdGlsbH0gID0gMi4xNDkgKyBcZnJhY3sxLjYwfXsxK2Veey0xLjgwMDQoZGF5c29uaG9wcy03Ljk1OCl9fSQNCg0KUGxvdHRpbmcgYW5kIGFuYWx5c2lzIG9wdGlvbnMgZm9yIG11bHRpbGV2ZWwgbW9kZWxzIGFyZSBsaW1pdGVkLCBzbyBsZXQncyBtb3ZlIGZvcndhcmQgd2l0aCBhIHNpbmdsZSBzZXQgb2YgY29uZGl0aW9ucyBhbmQgYSAnc2ltcGxlJyBzaW5nbGUtbGV2ZWwgbG9naXN0aWMgbW9kZWwgKGV4cHQ9MmEsIHJvdXNlPVRSVUUpLg0KDQoNCiMjIHVuaWxldmVsIGxvZ2lzdGljIG1vZGVsIHdpdGggY29uZmlkZW5jZSBpbnRlcnZhbHMgDQpgYGB7cn0NCmRmIDwtIEZQSGNhbGMgJT4lIGZpbHRlcihleHB0PT0iIDJBIiAmIHJvdXNlPT1UUlVFKSANCmRmJFBMQVRPLmhvcGRyb3AgPC0gZGYkaW5pdGlhbF9wbGF0byArIGRmJGRlbHRhLnBsYXRvDQp4IDwtIGFzLm51bWVyaWMoZGYkZGF5c29uaG9wcykNCnkgPC0gZGYkUExBVE8uaG9wZHJvcA0KDQojIyBjcmVhdGUgbW9kZWwNCm15bW9kZWw8LWRybSh5IH54LCBmY3QgPSBMTC40KCkpICM0LXBhcmFtZXRlciBsb2dpc3RpYyB3aXRoIGxpYnJhcnkoZHJjKQ0KDQpwbG90KG15bW9kZWwsIG1haW4gPSAiZGVmYXVsdCIpDQpwbG90KG15bW9kZWwsIGxvZz0iIiwgbWFpbiA9ICJub24tbG9nYXJpdGhtaWMgeC1heGlzIikNCg0KIyBjcmVhdGUgcHJlZGljdGlvbiBpbnRlcnZhbHMNCm5ld2RhdGEgPSBkYXRhLmZyYW1lKHkgPSB5LCB4ID0geCkNCmNvbmY5NSA8LSBhcy5kYXRhLmZyYW1lKHByZWRpY3QobXltb2RlbCwgbmV3ZGF0YSxpbnRlcnZhbCA9ICJjb25maWRlbmNlIiwgbGV2ZWwgPSAwLjk1KSkNCmNvbmY5NSR4IDwtIHgNCnByZWQ5NSA8LSBhcy5kYXRhLmZyYW1lKHByZWRpY3QobXltb2RlbCwgbmV3ZGF0YSwgaW50ZXJ2YWwgPSAicHJlZGljdGlvbiIsIGxldmVsID0gMC45NSkpDQpwcmVkOTUkeCA8LSB4DQpwcmVkOTk5IDwtIGFzLmRhdGEuZnJhbWUocHJlZGljdChteW1vZGVsLCBuZXdkYXRhLCBpbnRlcnZhbCA9ICJwcmVkaWN0aW9uIiwgbGV2ZWwgPSAwLjk5OSkpDQpwcmVkOTk5JHggPC0geA0KDQojIEFzeW1tZXRyaWMgcmliYm9ucyBiYXNlZCBvbiBwcmVkaWN0aW9uIGludGVydmFscw0KI25ld2RhdGEgPC0gZGF0YS5mcmFtZSh5ID0gYygzLjUsIDMsIDIuOSwgMi41LDEuOCwxLjcpLCB4ID0gYygxLDUsMTAsMjAsMzAsNDApKQ0KI25ld2RhdGEgPC0gdGlkeWZlcm1pICU+JSBmaWx0ZXIoYmF0Y2ggPSBTRUxFQ1RCQVRDSCkgJT4lIHNlbGVjdChkYXlzLHBsYXRvKSANCnFwbG90KGRhdGE9cHJlZDk1LCB4PXgsIHk9UHJlZGljdGlvbiwgeW1pbj1Mb3dlciwgeW1heD1QcmVkaWN0aW9uLCBnZW9tPSJyaWJib24iLCBmaWxsPUkoInJlZCIpLCBhbHBoYT1JKDAuMikpICsNCmdlb21fcmliYm9uKGRhdGE9cHJlZDk1LCBhZXMoeD14LCB5bWluPVByZWRpY3Rpb24sIHltYXg9VXBwZXIpLCBmaWxsPUkoImJsdWUiKSwgYWxwaGE9SSgwLjIpKSArDQpnZW9tX2xpbmUoZGF0YT1wcmVkOTUsIGFlcyh4PXgsIHk9UHJlZGljdGlvbiksIGNvbG9yPUkoImdyZWVuIiksIGx3ZD0xKSsNCmdlb21fcG9pbnQoZGF0YT1uZXdkYXRhLCBhZXMoeD14LCB5PXksIHltaW49TlVMTCwgeW1heD1OVUxMKSwgc2l6ZT0xLCBjb2w9ImJsdWUiKSsNCiB5bGFiKCJ5IikNCg0KIyBWaXN1YWxpc2UgaW50ZXJ2YWxzDQojIGJhc2VkIG9uIE1hdXJpdHMgRXZlcnMgKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQ5NDQ0NDg5L25vbmxpbmVhci1yZWdyZXNzaW9uLXByZWRpY3Rpb24taW4tcj9ycT0xKQ0KZGF0YS5mcmFtZSh4PXgsIHk9eSkgJT4lIGdncGxvdChhZXMoeCwgeSkpICsNCmdlb21fcG9pbnQoKSArDQpnZW9tX2xpbmUoZGF0YSA9IHByZWQ5NSwgYWVzKHggPSB4LCB5ID0gUHJlZGljdGlvbikpICsNCmdlb21fcmliYm9uKGRhdGEgPSBwcmVkOTk5LCBhZXMoeCA9IHgsIHltaW4gPSBMb3dlciwgeW1heCA9IFVwcGVyKSxmaWxsPUkoInBpbmsiKSxhbHBoYSA9IDAuNCkrDQpnZW9tX3JpYmJvbihkYXRhID0gcHJlZDk1LCBhZXMoeCA9IHgsIHltaW4gPSBMb3dlciwgeW1heCA9IFVwcGVyKSxmaWxsPUkoImdyZWVuIiksYWxwaGEgPSAwLjQpKw0KZ2VvbV9yaWJib24oZGF0YSA9IGNvbmY5NSwgYWVzKHggPSB4LCB5bWluID0gTG93ZXIsIHltYXggPSBVcHBlciksZmlsbD1JKCJwdXJwbGUiKSxhbHBoYSA9IDAuNCk7DQoNCmBgYA0KDQpOb3RlOiAgSWYgeW91IGNvbXBhcmUgdGhlIG11bHRpLWxldmVsIG1vZGVsIHBhcmFtZXRlciBlc3RpbWF0ZXMgZm9yIHJvdXNlPVRSVUUgd2l0aCB0aG9zZSBmb3IgdW5pbGV2ZWwgbW9kZWwgYmFzZWQgb24gcm91c2U9VFJVRSBzdWJzZXQsIHRoZSByZXN1bHRzIGFyZSBxdWl0ZSBzaW1pbGFyLCBidXQgbm90IGlkZW50aWNhbCENCg0KIyMgcGxvdCByZXNpZHVhbHMgb2Ygbm9ubGluZWFyIG1vZGVscyBnZW5lcmF0ZWQgaW4gbGlicmFyeShkcmMpDQpgYGB7cn0NCnBhcihtZnJvdyA9IGMoMiwgMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQoNCnBsb3QobS5NTS4zJHByZWRyZXMpDQpwbG90KG0uTEwuMyRwcmVkcmVzKQ0KcGxvdChtLkxMLjQkcHJlZHJlcykNCnBsb3QobS5MTC41JHByZWRyZXMpDQpgYGANCg0KDQojIHN1bW1hcnkNCg0KRm9yIGNhc2VzIHdoZXJlIDIwMTYgQ2VudGVubmlhbCBob3BzIGFyZSBhZGRlZCB0byB0aGlzIHBhcnRpY3VsYXIgYmFzZSBiZWVyIChzYW1wbGVkIGludG8gMTJveiBib3R0bGVzKSBuZWFyIHRoZSBlbmQgb2YgcHJpbWFyeSBmZXJtZW50YXRpb24gYXQgcm9vbSB0ZW1wZXJhdHVyZSBhbmQgYm90dGxlcyByZW1haW4gdW50b3VjaGVkIChzdGlsbCksIHRoZSA0LXBhcmFtZXRlciBsb2dpc3RpYyBlcXVhdGlvbiB3aXRoIHRoZSBmb2xsb3dpbmcgc2V0cyBvZiBwYXJhbWV0ZXJzIGlzIGEgcmVhc29uYWJsZSBtb2RlbCBvZiB0aGUgc3Vic2VxdWVudCBob3AtaW5kdWNlZCBwbGF0byBkcm9wOg0KDQokUExBVE9fe3N0aWxsfSAgPSAyLjE0OSArIFxmcmFjezEuNjB9ezErZV57LTEuODAwNChkYXlzb25ob3BzLTcuOTU4KX19JA0KDQoNClRoZSBmb2xsb3dpbmcgNC1wYXJhbWV0ZXIgbG9naXN0aWMgZXF1YXRpb24gaXMgYSByZWFzb25hYmxlIG1vZGVsIGZvciBjYXNlcyBpZGVudGljYWwgdG8gdGhlIGFib3ZlLCBidXQgd2hlcmUgYm90dGxlcyBhcmUgcm91c2VkIG9uY2UgZGFpbHk6DQoNCiRQTEFUT197cm91c2VkfSAgPSAxLjgwOSArIFxmcmFjezEuOTR9ezErZV57LTEuMzkyNShkYXlzb25ob3BzLTEyLjI3Mil9fSQNCg0KV2hlcmVhcyB0aGUgcmVzaWR1YWwgc3VtIG9mIHNxdWFyZXMgZm9yIHRoZSA1LXBhcmFtZXRlciBsb2dpc3RpYyBtb2RlbHMgd2FzIHNpbWlsYXIgdG8gdGhvc2Ugb2J0YWluZWQgZm9yIHRoZSA0LXBhcmFtZXRlciBlcXVhdGlvbiwgYSB2aXN1YWwgaW5zcGVjdGlvbiByZXZlYWxzIHRoYXQgdGhlIDQtcGFyYW1ldGVyIGVxdWF0aW9uIGlzIHByb2JhYmx5IHN1cGVyaW9yLCBpbiB0aGF0IHRoZSA0LXBhcmFtZXRlciBmaXQgYXBwYXJlbnRseSBkb2VzIG5vdCBbZmFsc2VseT9dIHByZWRpY3QgYSBsb25nIGxhZyB0aW1lIGluIHRoZSBjYXNlIG9mIHVucm91c2VkIHNhbXBsZXMuDQoNCk9ubHkgYSBzaW5nbGUgdmFyaWFibGUgKHJvdXNpbmcgdnMuIHN0aWxsKSBhbW9uZyBzZXZlcmFsIGh1bmRyZWQgdmFyaWFibGVzIGVuY29tcGFzc2luZyByYXcgbWF0ZXJpYWxzLCBtYWNoaW5lcywgcGVvcGxlIGFuZCBwcm9jZXNzIHJlc3VsdGVkIGluIHF1aXRlIGRpZmZlcmVudCBlc3RpbWF0ZWQgcGFyYW1ldGVycyBmb3IgYSA0LXBhcmFtZXRlciBsb2dpc3RpYyBtb2RlbC4gIFRoZXNlIHJlc3VsdHMgaGlnaGxpZ2h0IHRoZSBmYWN0IHRoYXQgYWxsIHByb2Nlc3MgdmFyaWFibGVzIHRoYXQgc2lnbmlmaWNhbnRseSBpbXBhY3QgdGhlIGVuZHBvaW50cyB3ZSB3aXNoIHRvIGNvbnRyb2wgbXVzdCB0aGVtc2VsdmVzIGJlIGRlZmluZWQsIGNvbnRyb2xsZWQgYW5kL29yIGRvY3VtZW50ZWQgaWYgbW9kZWxzIHN1Y2ggYXMgdGhlc2UgYXJlIHRvIGJlIHVzZWQgdG8gY2hhcmFjdGVyaXplIHRoZSBwcm9jZXNzIGluIHF1ZXN0aW9uLiAgDQoNCg0KIyBiaWdjaHVua190aWR5dHJhbnNmb3JtDQpgYGB7cn0NCm15ZGF0YSA8LXJlYWQuY3N2KCJ1amJjX2FfMTQ2OTA4MV9zbTU0OTYudHh0IixzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpICAjIyBTUEVDSUZZIGZpbGVuYW1lDQoNCiMjIGZpbGwgaW4gTUlTU0lORyBEQVRBIDooICBhZGQgdGltZSB6ZXJvIGRhdGEgZm9yIHRpbWUgc2VyaWVzIGFuYWx5c2lzKQ0KDQojIyBuZXN0ZWQgaWZlbHNlIHRvIGFkZCB0aW1lIHplcm8gcGxhdG8gdmFsdWVzIGZvciBlYWNoIGV4cHQ6DQpteWRhdGE8LSBteWRhdGEgJT4lIG11dGF0ZSguLCBpbml0aWFsX3BsYXRvID0gaWZlbHNlKGV4cHQ9PSIgMkEiLCAzLjc0LCBpZmVsc2UoZXhwdD09IiAxQSIsIDMuNjksIGlmZWxzZShleHB0PT0iIDFCIiwgMy41MywgaWZlbHNlKGV4cHQ9PSIgM0IiLCAzLjU0LCBpZmVsc2UoZXhwdD09IiA0QiIsIDQuNjYsIE5BKSkpKSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiMjIGFkZCBjb21wbGV0ZSB0aW1lLXplcm8gYW50b24gcGFhciBkYXRhIGZvciB0aW1lIHNlcmllcyAoZXhwdDJBKQ0KIyMgcXVhZHJ1cGxlLXVwIGVhY2ggdHJpcGxpY2F0ZSB0aW1lLXplcm8gbWVhc3VyZW1lbnQgKG9uZSBjb3B5IGZvciBlYWNoIE5IL0RIICYgcm91c2Uvc3RpbGwgY29tYm8pDQojIyBub3RlOiAgdGhpcyBwcmFjdGljZSBpcyBvZiBxdWVzdGlvbmFibGUgc3RhdGlzdGljYWwgcmlnb3IuICBJbiByZXRyb3NwZWN0IGl0IHdvdWxkIGhhdmUgYmVlbiBiZXR0ZXIgKGFuZCBhIGxvdCBlYXNpZXIpIHRvIGp1c3QgcnVuIDEyIHNhbXBsZXMgdGhyb3VnaCBBbnRvbiBvbiBkYXkwIQ0KdGltZXplcm8gPC0gbXlkYXRhICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBhcnJhbmdlKHNwZWNpYWxfZ3JvdXApICU+JSBzbGljZSgxOjEyKQ0KI2RhdGEuZW50cnkodGltZXplcm8pDQp0aW1lemVybyRUZXN0X0RhdGUgPC0gcmVwKGMoIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIpLDQpDQp0aW1lemVybyRUZXN0LnRpbWUgPC0gcmVwKGMoIjM6MjYgUE0iLCAiMzozMCBQTSIsICIzOjM0IFBNIiksNCkNCnRpbWV6ZXJvJEFCViA8LSA2LjY4DQp0aW1lemVybyRBQlcgPC0gNS4yDQp0aW1lemVybyRPRSA8LSAxNS45NQ0KdGltZXplcm8kRXIgPC0gNi4wOA0KdGltZXplcm8kRWEgPC0gcmVwKGMoMy43NCwgMy43MywgMy43NCksNCkNCnRpbWV6ZXJvJFNHIDwtIHJlcChjKDEuMDE0NjIsIDEuMDE0NjEsIDEuMDE0NjIpLDQpDQp0aW1lemVybyRSREYgPC0gcmVwKGMoNjMuODgsIDYzLjg5LCA2My44OCksNCkNCnRpbWV6ZXJvJEFERiA8LSByZXAoYyg3Ni41OCwgNzYuNjAsIDc2LjU4KSw0KQ0KdGltZXplcm8kQ2Fsb3JpZXMgPC0gcmVwKGMoMjE1LjM3LDIxNS4zNCwyMTUuMzUpLDQpDQp0aW1lemVyb1t0aW1lemVybyRob3A9PSJESCIsXSRjb250YWN0X2RheXMgPC0wDQp0aW1lemVyb1ssYygyODozMSldIDwtIDANCm15ZGF0YSA8LSByYmluZCh0aW1lemVybyxteWRhdGEpDQp3cml0ZS5jc3YobXlkYXRhLCAicXVpY2tpZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCm15ZGF0YTwtcmVhZC5jc3YoInF1aWNraWUuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQojIyMgIHRoaXMgaXMgYSByb3V0aW5lIGZvciBpbnNwZWN0aW5nIGRhdGEgIyMjDQpteWRhdGF0eXBlczwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2FwcGx5KG15ZGF0YSwgY2xhc3MpKSkgICMjIHRhYmxlIG9mIGRhdGF0eXBlcw0KbXlkYXRhdHlwZXMkaGVhZGVycyA8LSByb3duYW1lcyhteWRhdGF0eXBlcykgICAgIyMgY29udmVydCByb3duYW1lcyB0byB2YWx1ZXMNCm5hX2NvdW50IDwtYXMuZGF0YS5mcmFtZShzYXBwbHkobXlkYXRhLCBmdW5jdGlvbih5KSBzdW0obGVuZ3RoKHdoaWNoKGlzLm5hKHkpKSkpKSkgIyMgY291bnQgTkFzIGJ5IGNvbHVtbg0KbXlkYXRhT3ZlcnZpZXc8LWFzLmRhdGEuZnJhbWUoY2JpbmQobmFfY291bnQsbXlkYXRhdHlwZXMpKSAgIyMgdGFibGUgb2YgTkEgY291bnRzIGFuZCBkYXRhdHlwZXMNCm5hbWVzKG15ZGF0YU92ZXJ2aWV3KSA8LSBjKCJOQV9jb3VudCIsIkRhdGFfQ2xhc3MiLCAiSGVhZGVyIikNCnN1bShpcy5uYShteWRhdGEpKSAgIyMgdG90YWwgY291bnQgb2YgTkEgdmFsdWVzIGluIGVudGlyZSBzaGVldCAoMjMxIGluIHRoaXMgY2FzZSkNCm15ZGF0YU92ZXJ2aWV3ICU+JSBmaWx0ZXIoTkFfY291bnQ+MCkgICAgIyMgYnJlYWtkb3duIG9mIE5BIHZhbHVlcw0KDQpkYXRlY29sczwtIGRwdXQobmFtZXMoZHBseXI6OnNlbGVjdChteWRhdGEsIG1hdGNoZXMoImRhdGUiKSkpKSAgIyMgaGVhZGVycyBjb250YWluaW5nIHN0cmluZyAiZGF0ZSIgPT4gYygiYnJld19kYXRlIiwgInNhbXBsZV9jb2xsZWN0aW9uX2RhdGUiLCAiZHJ5aG9wX2RhdGUiLCAiVGVzdF9EYXRlIikNCiMjIEZPUkxPT1ANCmZvciAoaWNvbCBpbiBkYXRlY29scykgew0KICBuZXdjb2wgPSBwYXN0ZTAoaWNvbCwiLlBPU0lYY3QiKQ0KICMgcHJpbnQobmV3Y29sKQ0KICBteWRhdGFbLCBuZXdjb2xdID0gYXMuUE9TSVhjdChteWRhdGFbLCBpY29sXSxmb3JtYXQgPSAiJW0vJWQvJVkiKSAjIyMgIENSRUFURSBORVcgY29sdW1ucyB3aXRoIFBPU0lYY3QgICANCiMgIG15ZGF0YVssIG5ld2NvbF0gPSBhcy5QT1NJWGN0KGFzLm51bWVyaWMobXlkYXRhWywgaWNvbF0pICAqICg2MCo2MCoyNCksIG9yaWdpbj0iMTg5OS0xMi0zMCIpICMjIyAgbWljcm9zb2Z0IHRpbWVzDQp9DQoNCiMjIGNyZWF0ZSBkYXlvZmFkZGl0aW9uLCBkYXlzb25ob3BzLCBob3BzX2dfMTAwbUwsIHBvdW5kc19iYmwgdmFyaWFibGVzIHdpdGggZHBseXIgIm11dGF0ZSINCm15ZGF0YTwtIG15ZGF0YSAlPiUgDQogIG11dGF0ZShkYXlvZmFkZGl0aW9uID0gYXMuaW50ZWdlcihhcy5udW1lcmljKGRpZmZ0aW1lKGRyeWhvcF9kYXRlLlBPU0lYY3QsYnJld19kYXRlLlBPU0lYY3QpKSksDQogICAgICAgICBkYXlzb25ob3BzID0gYXMuaW50ZWdlcihhcy5udW1lcmljKGRpZmZ0aW1lKFRlc3RfRGF0ZS5QT1NJWGN0LGRyeWhvcF9kYXRlLlBPU0lYY3QpKS8oNjAqNjAqMjQpKSwNCiAgICAgICAgIGhvcHNfZ18xMDBtTCA9IChtZ19ob3BzL3ZvbHVtZV9tTCkvMTAsDQogICAgICAgICBwb3VuZHNfYmJsID0gKG1nX2hvcHMvdm9sdW1lX21MKSoxMTcvNDU0DQogICAgICAgICApDQogICAgICAgICANCm15ZGF0YSRPWDwtZ3JlcGwoIk9YIiwgbXlkYXRhJEhvcF90eXBlKSAgICAgICAjIyAgY3JlYXRlIGxvZ2ljYWwgIk9YIiBjb2x1bW4NCm15ZGF0YSRyb3VzZTwtZ3JlcGwoInJvdXNlIiwgbXlkYXRhJHNwZWNpYWxfZ3JvdXApIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJEdyaW5kPC1ncmVwbCgiR3JpbmQiLCBteWRhdGEkSG9wX3R5cGUpICAgICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkQ29uZTwtZ3JlcGwoIkNvbmUiLCBteWRhdGEkSG9wX3R5cGUpICAgICAgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRoYXJ2ZXN0MjAxNDwtZ3JlcGwoIjE0IiwgbXlkYXRhJEhvcF90eXBlKSAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJGhhcnZlc3QyMDE1PC1ncmVwbCgiMTUiLCBteWRhdGEkSG9wX3R5cGUpICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkaGFydmVzdDIwMTc8LWdyZXBsKCIxNyIsIG15ZGF0YSRIb3BfdHlwZSkgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCg0KIyMgaWZlbHNlIHN0YXRlbWVudCBmb3IgaGFydmVzdHllYXIgKGlmIG5laXRoZXIgMjAxNCBub3IgMjAxNSBub3IgMjAxNywgdGhlbiAyMDE2KQ0KbXlkYXRhJGhhcnZlc3RZZWFyIDwtIGlmZWxzZSgNCiAgbXlkYXRhJGhhcnZlc3QyMDE0PT1UUlVFLCAyMDE0LA0KICBpZmVsc2UobXlkYXRhJGhhcnZlc3QyMDE1PT1UUlVFLCAyMDE1LCANCiAgICAgICAgIGlmZWxzZShteWRhdGEkaGFydmVzdDIwMTc9PVRSVUUsIDIwMTcsIDIwMTYpKSkNCg0KIyMgaWZlbHNlIHN0YXRlbWVudCBmb3IgZm9ybSBvZiBob3BzIChpZiBuZWl0aGVyIGNvbmUgbm9yIGdyb3VuZCBub3IgTkgsIHRoZW4gcGVsbGV0KSANCm15ZGF0YSRmb3JtX29mX2hvcHMgPC0gaWZlbHNlKA0KICBteWRhdGEkQ29uZT09VFJVRSwgImNvbmUiLA0KICBpZmVsc2UobXlkYXRhJEdyaW5kPT1UUlVFLCAiZ3JvdW5kIiwNCiAgICAgICAgIGlmZWxzZShteWRhdGEkSG9wX3R5cGU9PSJOSCIsICJOSCIsICJwZWxsZXQiKSkpDQoNCiMjIGlmZWxzZSBzdGF0ZW1lbnQgZm9yIHRlbXBlcmF0dXJlIGdyZWF0ZXIgb3IgbGVzcyB0aGFuIDEwIA0KbXlkYXRhJERIX3RlbXAgPC0gaWZlbHNlKA0KICBteWRhdGEkdGVtcC5DPDEwLCAiY29sZCIsDQogIGlmZWxzZShteWRhdGEkdGVtcC5DPjEwLCAid2FybSIsICJzb21ldGhpbmcgZWxzZSIpKQ0KDQpteWRhdGEkdmFyaWV0eTwtbXlkYXRhJEhvcF90eXBlDQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIlswLTldKyIsIiIsIG15ZGF0YSR2YXJpZXR5KSAjIyByZW1vdmUgYWxsIG51bWJlcnMNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiT1giLCIiLCBteWRhdGEkdmFyaWV0eSkgICAgICMjIHJlbW92ZSBzcGVjaWZpYyB0ZXh0DQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIkdyaW5kIiwiIiwgbXlkYXRhJHZhcmlldHkpDQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIkNvbmUiLCIiLCBteWRhdGEkdmFyaWV0eSkNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiICIsIiIsIG15ZGF0YSR2YXJpZXR5KSAgICAgICMjIHJlbW92ZSBzcGFjZXMNCg0KbXlkYXRhPC0gbXlkYXRhICU+JSBtdXRhdGUoRVhQVG5ldz1wYXN0ZTAoImdyb3VwIixleHB0LCBzdWJzdHIoc3BlY2lhbF9ncm91cCwgMSwyKSxhcy5jaGFyYWN0ZXIocm91c2UpKSkNCiMgY2xlYW4gaXQgdXAgYnkgcmVtb3ZpbmcgIk5BIiBhbmQgYW55IHNwYWNlcyBkdWUgdG8gY2FuYXJ5Y29kZSBidWcNCm15ZGF0YSRFWFBUbmV3IDwtIGdzdWIoIiAiLCIiLCBteWRhdGEkRVhQVG5ldykgICMjIHJlbW92ZSBhbnkgc3BhY2VzDQpteWRhdGEkRVhQVG5ldyA8LSBnc3ViKCJOQSIsIiIsIG15ZGF0YSRFWFBUbmV3KSAjIyByZW1vdmUgIk5BIg0KDQojIyBzZWxlY3QgYW5kIHJlYXJyYW5nZSBjb2x1bW5zIA0KbXlkYXRhIDwtIG15ZGF0YSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2FtcGxlX2lkLGV4cHQsIEVYUFRuZXcsIGhvcCwgQklOaG9wLCB2YXJpZXR5LCBPWCwgaGFydmVzdFllYXIsIGZvcm1fb2ZfaG9wcyxzcGVjaWFsX2NvbmRpdGlvbnMsIHJvdXNlLCBkYXlzb25ob3BzLCBkYXlvZmFkZGl0aW9uLCBESF90ZW1wLCB0ZW1wLkMsIGhvcHNfZ18xMDBtTCwgcG91bmRzX2JibCwNCiAgICAgICAgIEFCViwgQUJXLCBPRSwgRXIsIEVhLCBTRywgUkRGLCBBREYsIENhbG9yaWVzLCANCiAgICAgICAgIGRob3BfZGF5LCBjb250YWN0X2RheXMsIFJFRl9OSCwgQUJWX2luY3JlYXNlLCANCiAgICAgICAgIGJyZXdfZGF0ZS5QT1NJWGN0LCBzYW1wbGVfY29sbGVjdGlvbl9kYXRlLlBPU0lYY3QsIGRyeWhvcF9kYXRlLlBPU0lYY3QsVGVzdF9EYXRlLlBPU0lYY3QsIGluaXRpYWxfcGxhdG8pDQojIyByZW1vdmUgIi5QT1NJWGN0IiBzdWZmaXguICBMZWF2aW5nIGl0IGFzLWlzIHdpbGwgb25seSBhZGQgdG8gY29uZnVzaW9uIGlmL3doZW4gdGhlc2UgZGF0YSBhcmUgc2F2ZWQgYW5kIHJlLWltcG9ydGVkIChhbmQgYmVjb21lICdjaGFyYWN0ZXInIGZvcm1hdCEpDQpjb2xuYW1lcyhteWRhdGEpID0gZ3N1YigiLlBPU0lYY3QiLCAiIiwgY29sbmFtZXMobXlkYXRhKSkNCiMjY29tcHV0ZSBtZWFuIE5IIChjb250cm9sKSB2YWx1ZXMgDQojIyBtZWFuIE5IIChjb250cm9sKSB2YWx1ZXMgZm9yIGVhY2ggRVhQVG5ldyBncm91cA0KbWVhbi5jb250cm9sX05IPC0gbXlkYXRhICU+JSANCiAgZ3JvdXBfYnkoRVhQVG5ldykgJT4lDQogIGZpbHRlcihob3A9PSJOSCIpICU+JQ0KICBzdW1tYXJpc2VfYXQodmFycyhBQlYsIEFCVywgRWEsIFNHKSxmdW5zKG1lYW4sIG4oKSkpDQojIyByZXBsYWNlICJfbWVhbiIgd2l0aCAiLmNvbnRyb2xfTkgiIGluIGNvbHVtbiBuYW1lcw0KY29sbmFtZXMobWVhbi5jb250cm9sX05IKSA9IGdzdWIoIl9tZWFuIiwgIi5jb250cm9sX05IIiwgY29sbmFtZXMobWVhbi5jb250cm9sX05IKSkNCiMjY29tcHV0ZSAkXERlbHRhJCB2YWx1ZXMgKGNoYW5nZXMgcmVsYXRpdmUgdG8gdW4tZHJ5aG9wcGVkIGNvbnRyb2xzKSB1c2luZyBvYmplY3RzIGNyZWF0ZWQgYWJvdmUuLi4NCiMjIGZpcnN0IGpvaW4gb3VyIGRhdGEgd2l0aCBtZWFuIEFCViBmb3IgdW5ob3BwZWQgc2FtcGxlcyBpbiBnaXZlbiBleHBlcmltZW50IChtZWFuLmNvbnRyb2xfTkg7IGNhbGN1bGF0ZWQgYWJvdmUpDQoNCkZQSGNhbGM8LSBsZWZ0X2pvaW4obXlkYXRhLCBtZWFuLmNvbnRyb2xfTkgsIGJ5PSJFWFBUbmV3IikNCg0KIyMgY2FsY3VsYXRlIEFCV19pbmNyZWFzZSBieSBzdWJ0cmFjdGluZyBlYWNoIGluZGl2aWR1YWwgQUJXIG1lYXN1cmVtZW50IGZyb20gcmVzcGVjdGl2ZSBtZWFuLmNvbnRyb2xfTkg6DQpGUEhjYWxjJGRlbHRhLkFCViA8LSBGUEhjYWxjJEFCViAtIEZQSGNhbGMkQUJWLmNvbnRyb2xfTkgNCkZQSGNhbGMkZGVsdGEuQUJXIDwtIEZQSGNhbGMkQUJXIC0gRlBIY2FsYyRBQlcuY29udHJvbF9OSA0KRlBIY2FsYyRkZWx0YS5wbGF0byA8LSBGUEhjYWxjJEVhIC0gRlBIY2FsYyRFYS5jb250cm9sX05IDQpGUEhjYWxjJGRlbHRhLlNHIDwtIEZQSGNhbGMkU0cgLSBGUEhjYWxjJFNHLmNvbnRyb2xfTkgNCg0KIyMgdGhlIGNvbnRyb2wgc2FtcGxlcyBoYXZlIHNlcnZlZCB0aGVpciBwdXJwb3NlLCBub3cgcmVtb3ZlIHRoZW0gZnJvbSBkYXRhc2V0LiBUaGUgZm9sbG93aW5nIGNhbGN1bGF0aW9ucyBhcmUgb25seSBtZWFuaW5nZnVsIGZvciBkcnktaG9wcGVkIHNhbXBsZXMuICANCkZQSGNhbGM8LSBGUEhjYWxjICU+JSBmaWx0ZXIoaG9wPT0iREgiKQ0KIyMgKmNhbGNhbGF0ZSogY29ycmVzcG9uZGluZyBDTzIgcHJvZHVjdGlvbiANCkZQSGNhbGMkY2FsY0NPMl9pbmNyZWFzZSA8LSBGUEhjYWxjJGRlbHRhLkFCVyooMC40NC8wLjQ2KQ0KIyNjb252ZXJ0IGNhbGNDTzJfaW5jcmVhc2UgKGluIGcvMTAwbUwpIHRvIGNhbGN1bGF0ZWQgQ08yIHZvbHVtZXMgYWRkZWQNCiMjIGcvTCA9IDEwKiBnLzEwMG1MDQojIyBUaGUgY29udmVyc2lvbiBmYWN0b3IgZnJvbSB2b2x1bWVzIG9mIENPMiB0byBDTzIgYnkgd2VpZ2h0IChnL0wpIGlzIDEuOTYuIEZvciBleGFtcGxlOiAyLjUgdm9sdW1lcyB4IDEuOTYgPSA0LjkgZy9sLg0KRlBIY2FsYyRjYWxjQ08ydm9sc19pbmNyZWFzZSA8LSBGUEhjYWxjJGNhbGNDTzJfaW5jcmVhc2UqMTAvMS45Ng0KIyMgZGVmaW5lICJGUEgiIGFzIGFtb3VudCBwcm9kdWNlZCBwZXIgJSBkcnktaG9wcyBhZGRlZCAoaW4gZy8xMDBtTCk6DQojIyAgRlBIID0gRm9sZCBQcm9kdWN0aW9uIGR1ZSB0byBIb3BzIChmb2xkLWluY3JlYXNlIGJ5IG1hc3M6IGFtb3VudCBvZiBnaXZlbiBlbmRwb2ludCByZWxhdGl2ZSB0byBhbW91bnQgb2YgaG9wcyBhZGRlZCkNCiMjICANCkZQSGNhbGMkRlBIX0V0T0ggPSBGUEhjYWxjJGRlbHRhLkFCVy9GUEhjYWxjJGhvcHNfZ18xMDBtTA0KRlBIY2FsYyRGUEhfQ08yID0gRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlL0ZQSGNhbGMkaG9wc19nXzEwMG1MDQpGUEhjYWxjJEZQSF9wbGF0byA9IEZQSGNhbGMkZGVsdGEucGxhdG8vRlBIY2FsYyRob3BzX2dfMTAwbUwNCg0KIyMgcmVwbGFjaW5nIHRpbWUtemVybyB0aW1lIHZhbHVlcyB3aXRoIGEgdmVyeSBzbWFsbCBudW1iZXIgKHJhdGhlciB0aGFuIGV4YWN0bHkgemVybykgd2lsbCBwcmV2ZW50IGlzc3VlcyB3aXRoIGFuYWx5c2lzIG9mIG5vbmxpbmVhciBtb2RlbHMNCkZQSGNhbGNbRlBIY2FsYyRkYXlzb25ob3BzPT0wLF0kZGF5c29uaG9wcyA8LSAwLjAxDQoNCiMjIGFuZCBzYXZlIHRoZSB0cmFuc2Zvcm1lZCBkYXRhIHRvIGNzdjoNCndyaXRlLmNzdihGUEhjYWxjLCJGUEhjYWxjLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KRlBIY2FsYyA8LSByZWFkLmNzdigiRlBIY2FsYy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkNCg0KZGY8LSBGUEhjYWxjICU+JSBncm91cF9ieShFWFBUbmV3KSAlPiUNCiAgc3VtbWFyaXNlX2F0KHZhcnMoaG9wc19nXzEwMG1MLHBvdW5kc19iYmwsZGVsdGEuQUJWLCBkZWx0YS5BQlcsIGRlbHRhLnBsYXRvLCBGUEhfcGxhdG8sIEZQSF9FdE9ILCBGUEhfQ08yLCBjYWxjQ08ydm9sc19pbmNyZWFzZSksZnVucyhyb3VuZChtZWFuKC4pLCAyKSkpICU+JQ0KICBhcnJhbmdlKGRlc2MoRlBIX0V0T0gpKSANCmRmDQpgYGANCg0KDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQo=